feat: move-to-folder dialog, batch actions, reader source view, Focused/Other pivot

- MoveToFolderDialog: modal folder picker per account (excludes current)
- Wire 'Mover a...' in reader ⋯ menu + batch move (emit -1 -> dialog)
- Connect batch delete/mark-unread/flag handlers (were unconnected)
- Reader: 'Ver codigo fuente' reads .eml via MimeStorageService::readEmlFile
- Reader: 'Marcar como no leido' emits markUnreadRequested
- Pivot Focused/Other: EmailListModel::setShowFocusedOnly heuristic
  (flagged/pinned/frequent senders), reset to Focused on inbox select
This commit is contained in:
2026-08-30 02:26:31 +02:00
parent 59692b5706
commit 6e4a588879
11 changed files with 292 additions and 15 deletions
+110 -1
View File
@@ -8,6 +8,7 @@
#include "services/rulesengine.h"
#include "ui/rulesmanagerdialog.h"
#include "ui/accountsetupdialog.h"
#include "ui/movetofolderdialog.h"
#include "ui/delegates/FolderTreeDelegate.h"
#include "services/preferencesservice.h"
#include "services/startupbehaviorservice.h"
@@ -268,6 +269,10 @@ void MainMainWindow::setupMailPage()
connect(m_mailListView, &MailListView::replyRequested, this, &MainMainWindow::onReplyRequested);
connect(m_mailListView, &MailListView::forwardRequested, this, &MainMainWindow::onForwardRequested);
connect(m_mailListView, &MailListView::markUnreadRequested, this, &MainMainWindow::onMarkUnreadRequested);
connect(m_mailListView, &MailListView::batchMoveRequested, this, &MainMainWindow::onBatchMoveRequested);
connect(m_mailListView, &MailListView::batchDeleteRequested, this, &MainMainWindow::onBatchDeleteRequested);
connect(m_mailListView, &MailListView::batchMarkUnreadRequested, this, &MainMainWindow::onBatchMarkUnreadRequested);
connect(m_mailListView, &MailListView::batchFlagRequested, this, &MainMainWindow::onBatchFlagRequested);
m_folderSplitter->addWidget(m_mailListView);
// Viewer stack: placeholder, reader, compose
@@ -299,6 +304,7 @@ void MainMainWindow::setupMailPage()
connect(m_emailViewer, &ReaderView::replyRequested, this, &MainMainWindow::onReaderReplyRequested);
connect(m_emailViewer, &ReaderView::forwardRequested, this, &MainMainWindow::onReaderForwardRequested);
connect(m_emailViewer, &ReaderView::deleteRequested, this, &MainMainWindow::onReaderDeleteRequested);
connect(m_emailViewer, &ReaderView::markUnreadRequested, this, &MainMainWindow::onMarkUnreadRequested);
connect(m_emailViewer, &ReaderView::detachRequested, this, [this]() {
if (m_currentMailId >= 0) {
openMailInIndependentWindow(m_currentMailId);
@@ -412,6 +418,8 @@ void MainMainWindow::onFolderSelected(const QModelIndex &index)
m_mailService->fetchMails(QString::number(account->id()), QString::number(m_currentFolderId));
// Tell MailListView the folder type for Focused/Other pivot
m_mailListView->setFolderType(folder.type().toLower()); // "inbox", "sent", etc.
// Reset pivot to Focused for inbox folders
m_mailListView->resetPivot();
delete account;
}
}
@@ -525,6 +533,107 @@ void MainMainWindow::onDeleteRequested(int mailId)
statusBar()->showMessage(tr("Mensaje eliminado"), 3000);
}
void MainMainWindow::onMoveMailToFolderRequested(int mailId)
{
// Determine the account of the mail (via its current folder).
std::optional<MailItem> optMail = MailItemDao::findById(mailId);
if (!optMail) {
statusBar()->showMessage(tr("Mensaje no encontrado"), 3000);
return;
}
int currentFolderId = optMail->folderId();
std::optional<Folder> optFolder = FolderDao::findById(currentFolderId);
if (!optFolder) {
statusBar()->showMessage(tr("Carpeta no encontrada"), 3000);
return;
}
int accountId = optFolder->accountId();
QVector<Folder> folders = FolderDao::findByAccountId(accountId);
MoveToFolderDialog dlg(folders, currentFolderId, this);
if (dlg.exec() != QDialog::Accepted) return;
int targetFolderId = dlg.selectedFolderId();
if (targetFolderId < 0) return;
m_mailService->moveMail(QString::number(mailId), QString::number(targetFolderId));
// MailService::moveMail emits mailMoved; here we also refresh locally.
if (m_currentMailId == mailId) m_currentMailId = -1;
m_emailModel->refresh();
m_mailListView->treeView()->clearSelection();
m_viewerStack->setCurrentIndex(0);
statusBar()->showMessage(tr("Correo movido"), 3000);
}
void MainMainWindow::onBatchMoveRequested(const QVector<int> &mailIds, int targetFolderId)
{
if (mailIds.isEmpty()) return;
// Resolve target folder. targetFolderId == -1 → ask the user via dialog.
int resolvedTarget = targetFolderId;
if (resolvedTarget < 0) {
// Use the account of the first mail.
std::optional<MailItem> optMail = MailItemDao::findById(mailIds.first());
if (!optMail) return;
std::optional<Folder> optFolder = FolderDao::findById(optMail->folderId());
if (!optFolder) return;
QVector<Folder> folders = FolderDao::findByAccountId(optFolder->accountId());
MoveToFolderDialog dlg(folders, optMail->folderId(), this);
if (dlg.exec() != QDialog::Accepted) return;
resolvedTarget = dlg.selectedFolderId();
if (resolvedTarget < 0) return;
}
for (int mailId : mailIds) {
m_mailService->moveMail(QString::number(mailId), QString::number(resolvedTarget));
if (m_currentMailId == mailId) m_currentMailId = -1;
}
m_emailModel->refresh();
m_mailListView->treeView()->clearSelection();
m_viewerStack->setCurrentIndex(0);
statusBar()->showMessage(tr("%1 correo(s) movidos").arg(mailIds.size()), 3000);
}
void MainMainWindow::onBatchDeleteRequested(const QVector<int> &mailIds)
{
if (mailIds.isEmpty()) return;
for (int mailId : mailIds) {
MailItemDao::remove(mailId);
if (m_currentMailId == mailId) m_currentMailId = -1;
}
m_emailModel->refresh();
m_mailListView->treeView()->clearSelection();
m_viewerStack->setCurrentIndex(0);
statusBar()->showMessage(tr("%1 correo(s) eliminados").arg(mailIds.size()), 3000);
}
void MainMainWindow::onBatchMarkUnreadRequested(const QVector<int> &mailIds)
{
if (mailIds.isEmpty()) return;
for (int mailId : mailIds) {
auto item = MailItemDao::findById(mailId);
if (item.has_value()) {
item->setRead(false);
MailItemDao::update(*item);
}
}
m_emailModel->refresh();
statusBar()->showMessage(tr("%1 correo(s) marcados como no leídos").arg(mailIds.size()), 3000);
}
void MainMainWindow::onBatchFlagRequested(const QVector<int> &mailIds, bool flagged)
{
if (mailIds.isEmpty()) return;
for (int mailId : mailIds) {
auto item = MailItemDao::findById(mailId);
if (item.has_value()) {
item->setFlagged(flagged);
MailItemDao::update(*item);
}
}
m_emailModel->refresh();
statusBar()->showMessage(tr("%1 correo(s) actualizados").arg(mailIds.size()), 3000);
}
void MainMainWindow::onCategoryRequested(int mailId)
{
// Show category context menu at cursor position
@@ -538,7 +647,7 @@ void MainMainWindow::onMoreRequested(int mailId, const QPoint &globalPos)
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("Mover a...", [this, mailId]() { onMoveMailToFolderRequested(mailId); });
menu.addAction("Marcar como no leído", [this, mailId]() {
auto item = MailItemDao::findById(mailId);
if (item.has_value()) {