feat: mark-spam, rule priority persistence, run-rules-on-selected, icons fix, scheduled send + reader polish

- Mark as spam: moves mail to Spam/Junk folder of account
- Rule priority: persistPriorityOrder persists 1..N via RuleDao::update
- Run rules on selected: uses MailListView::selectedMailIds() + onRunRulesOnSelected
- Icons: switch code to qrc short aliases (close/send/bold/etc) + new detach.svg
  -> resolves all 'Cannot open file' SVG warnings at startup
- Categories: CompactMailDelegate paints category chips from CategoryDao
- Scheduled send: MailService::scheduleSend with QTimer; wire both composer paths
- Reader: show real 'Para:' recipient; delete attachment removes stored file
This commit is contained in:
2026-08-30 02:37:46 +02:00
parent 6e4a588879
commit 52b7a86fa1
13 changed files with 195 additions and 35 deletions
+63 -16
View File
@@ -108,10 +108,6 @@ void MainMainWindow::setupUI()
// Page 1: Compose
m_composeView = new ComposeView();
connect(m_composeView, &ComposeView::sendRequested, [this](const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, const QDateTime &scheduleTime, const QString &fromAddr, const QStringList &attachmentPaths) {
if (scheduleTime.isValid()) {
statusBar()->showMessage(tr("Scheduled sending is not available yet"), 5000);
return;
}
if (fromAddr.isEmpty()) {
statusBar()->showMessage(tr("Select an account before sending"), 5000);
return;
@@ -124,8 +120,13 @@ void MainMainWindow::setupUI()
mail.setSubject(subject);
mail.setBodyHtml(body);
mail.setDate(QDateTime::currentDateTimeUtc());
m_mailService->sendMail(mail, fromAddr, attachmentPaths);
statusBar()->showMessage(tr("Sending message…"), 5000);
if (scheduleTime.isValid()) {
m_mailService->scheduleSend(mail, fromAddr, attachmentPaths, scheduleTime);
statusBar()->showMessage(tr("Envío programado"), 5000);
} else {
m_mailService->sendMail(mail, fromAddr, attachmentPaths);
statusBar()->showMessage(tr("Sending message…"), 5000);
}
switchToPage(PageMail);
});
connect(m_composeView, &ComposeView::discardRequested, [this]() {
@@ -305,6 +306,7 @@ void MainMainWindow::setupMailPage()
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::markSpamRequested, this, &MainMainWindow::onMarkSpamRequested);
connect(m_emailViewer, &ReaderView::detachRequested, this, [this]() {
if (m_currentMailId >= 0) {
openMailInIndependentWindow(m_currentMailId);
@@ -685,6 +687,39 @@ void MainMainWindow::onMarkUnreadRequested(int mailId)
}
}
void MainMainWindow::onMarkSpamRequested(int mailId)
{
std::optional<MailItem> optMail = MailItemDao::findById(mailId);
if (!optMail) return;
std::optional<Folder> curFolder = FolderDao::findById(optMail->folderId());
if (!curFolder) return;
int accountId = curFolder->accountId();
// Find a Spam/Junk folder for this account.
QVector<Folder> folders = FolderDao::findByAccountId(accountId);
int spamFolderId = -1;
for (const Folder &f : folders) {
QString n = f.name().toLower();
if (n == "spam" || n == "junk" || n.contains("spam") || n.contains("junk")) {
spamFolderId = f.id();
break;
}
}
if (spamFolderId < 0) {
QMessageBox::information(this, tr("Marcar como spam"),
tr("No hay una carpeta de Spam en esta cuenta. Mueve el correo manualmente con «Mover a...»."));
return;
}
m_mailService->moveMail(QString::number(mailId), QString::number(spamFolderId));
if (m_currentMailId == mailId) m_currentMailId = -1;
m_emailModel->refresh();
m_mailListView->treeView()->clearSelection();
m_viewerStack->setCurrentIndex(0);
statusBar()->showMessage(tr("Marcado como spam y movido a la carpeta de spam"), 3000);
}
void MainMainWindow::onNewMessage()
{ // Show compose view in the viewer stack
m_embeddedComposeView->initializeComposition();
@@ -784,10 +819,21 @@ void MainMainWindow::onRunRulesOnFolder()
void MainMainWindow::onRunRulesOnSelected()
{
// Get selected mail IDs
QMessageBox::information(this, tr("Ejecutar reglas"), tr("Ejecutando reglas en correos seleccionados..."));
// TODO: Get selected mail IDs from MailListView
statusBar()->showMessage(tr("Reglas ejecutadas"), 3000);
QVector<int> ids = m_mailListView->selectedMailIds();
if (ids.isEmpty() && m_currentMailId >= 0) {
ids.append(m_currentMailId);
}
if (ids.isEmpty()) {
statusBar()->showMessage(tr("Selecciona al menos un correo"), 3000);
return;
}
QVector<qint64> mailIds;
for (int id : ids) mailIds.append(id);
int processed = RulesEngine::runRulesOnMails(mailIds);
statusBar()->showMessage(tr("Reglas ejecutadas en %1 correos").arg(processed), 3000);
m_emailModel->refresh();
m_categoryTree->refresh();
}
void MainMainWindow::showRulesManager()
@@ -1197,10 +1243,6 @@ void MainMainWindow::onEmbeddedSendRequested(const QString &to, const QString &c
const QString &fromAddress,
const QStringList &attachmentPaths)
{
if (scheduleTime.isValid()) {
statusBar()->showMessage(tr("Scheduled sending is not available yet"), 5000);
return;
}
if (fromAddress.isEmpty()) {
statusBar()->showMessage(tr("Select an account before sending"), 5000);
return;
@@ -1213,8 +1255,13 @@ void MainMainWindow::onEmbeddedSendRequested(const QString &to, const QString &c
mail.setSubject(subject);
mail.setBodyHtml(body);
mail.setDate(QDateTime::currentDateTimeUtc());
m_mailService->sendMail(mail, fromAddress, attachmentPaths);
statusBar()->showMessage(tr("Sending message…"), 5000);
if (scheduleTime.isValid()) {
m_mailService->scheduleSend(mail, fromAddress, attachmentPaths, scheduleTime);
statusBar()->showMessage(tr("Envío programado"), 5000);
} else {
m_mailService->sendMail(mail, fromAddress, attachmentPaths);
statusBar()->showMessage(tr("Sending message…"), 5000);
}
m_viewerStack->setCurrentIndex(0); // placeholder
m_embeddedComposeView->initializeComposition();
}