Actualizaciones varias: mejoras en cuenta, sincronización, UI

This commit is contained in:
2026-07-05 10:45:44 +02:00
parent 8799633447
commit 4569c174f0
195 changed files with 32571 additions and 20694 deletions
+84 -26
View File
@@ -5,14 +5,17 @@
#include <QDebug>
#include <QRegularExpression>
#include <QRegularExpressionValidator>
#include <QIntValidator>
#include <QTimer>
// ─────────────────────── Constructor ───────────────────────
AccountSetupDialog::AccountSetupDialog(AccountService *accountService, QWidget *parent)
: QDialog(parent)
, m_accountService(accountService)
, m_selectedProvider(0)
, m_accountCreatedOk(false)
, m_editingAccountId(-1)
, m_isEditing(false)
{
m_authTimeoutTimer = new QTimer(this);
m_authTimeoutTimer->setSingleShot(true);
@@ -26,8 +29,44 @@ AccountSetupDialog::AccountSetupDialog(AccountService *accountService, QWidget *
setupUI();
}
// ─────────────────────── UI Setup ───────────────────────
// ─────────────────────── Public API ───────────────────────
void AccountSetupDialog::loadAccountForEditing(const Account &account)
{
// Only support editing IMAP accounts for simplicity
if (account.type() != AccountType::IMAP) {
QMessageBox::information(this, tr("Edit Account"),
tr("Editing of OAuth (Gmail/Outlook) accounts is not supported in this version.\n"
"You can add a new account instead."));
m_isEditing = false;
return;
}
m_isEditing = true;
m_editingAccountId = account.id();
// Set window title
setWindowTitle(tr("Edit Email Account Wino Mail"));
// Fill IMAP fields
m_imapEmailEdit->setText(account.email());
m_imapNameEdit->setText(account.displayName());
// Password field left blank for security; user must re-enter
m_imapPasswordEdit->clear();
const Account::ConnectionSettings &settings = account.connectionSettings();
m_imapHostEdit->setText(settings.incomingHost);
m_imapPortEdit->setText(QString::number(settings.incomingPort));
m_smtpHostEdit->setText(settings.outgoingHost);
m_smtpPortEdit->setText(QString::number(settings.outgoingPort));
m_sslCheckbox->setChecked(settings.incomingSsl); // assuming same for SMTP
// Switch to IMAP page
m_selectedProvider = 2; // IMAP
goToPage(PageImap);
}
// ─────────────────────── UI Setup ───────────────────────
void AccountSetupDialog::setupUI()
{
setWindowTitle("Añadir Cuenta de Correo — Wino Mail");
@@ -105,7 +144,6 @@ void AccountSetupDialog::setupUI()
}
// ─────────────────── Page 0: Provider ───────────────────
QWidget* AccountSetupDialog::createProviderPage()
{
QWidget *page = new QWidget();
@@ -165,7 +203,6 @@ QWidget* AccountSetupDialog::createProviderPage()
}
// ─────────────────── Page 1: OAuth ───────────────────
QWidget* AccountSetupDialog::createOAuthPage()
{
QWidget *page = new QWidget();
@@ -178,12 +215,12 @@ QWidget* AccountSetupDialog::createOAuthPage()
lay->addWidget(title);
QLabel *info = new QLabel(
"Se abrirá tu navegador web para que inicies sesión de forma segura.\n"
"Wino Mail no almacena tu contraseña, solo el token de acceso autorizado.\n\n"
"Pasos:\n"
" 1. Introduce tu dirección de correo.\n"
" 2. Pulsa «Autenticar en Navegador».\n"
" 3. Completa el inicio de sesión en la ventana del navegador.\n"
"Se abrirá tu navegador web para que inicies sesión de forma segura.\\n"
"Wino Mail no almacena tu contraseña, solo el token de acceso autorizado.\\n\\n"
"Pasos:\\n"
" 1. Introduce tu dirección de correo.\\n"
" 2. Pulsa «Autenticar en Navegador».\\n"
" 3. Completa el inicio de sesión en la ventana del navegador.\\n"
" 4. Al terminar, esta ventana se actualizará automáticamente."
);
info->setWordWrap(true);
@@ -224,7 +261,6 @@ QWidget* AccountSetupDialog::createOAuthPage()
}
// ─────────────────── Page 2: IMAP ───────────────────
QWidget* AccountSetupDialog::createImapPage()
{
QWidget *page = new QWidget();
@@ -308,7 +344,6 @@ QWidget* AccountSetupDialog::createImapPage()
}
// ─────────────────── Page 3: Progress ───────────────────
QWidget* AccountSetupDialog::createProgressPage()
{
QWidget *page = new QWidget();
@@ -340,7 +375,6 @@ QWidget* AccountSetupDialog::createProgressPage()
}
// ─────────────────── Navigation ───────────────────
void AccountSetupDialog::goToPage(int page)
{
m_stack->setCurrentIndex(page);
@@ -364,7 +398,7 @@ void AccountSetupDialog::updateNavButtons()
m_btnNext->setVisible(false); // OAuth flow is driven by the authenticate button
break;
case PageImap:
m_btnNext->setText("Conectar");
m_btnNext->setText(m_isEditing ? "Guardar cambios" : "Conectar");
m_btnNext->setEnabled(true);
m_btnNext->setVisible(true);
break;
@@ -423,7 +457,6 @@ void AccountSetupDialog::onCancelClicked()
}
// ─────────────────── OAuth Flow ───────────────────
void AccountSetupDialog::startOAuthAuthentication()
{
QString email = m_oauthEmailEdit->text().trimmed();
@@ -458,7 +491,6 @@ void AccountSetupDialog::onAuthTimeout()
}
// ─────────────────── IMAP Flow ───────────────────
void AccountSetupDialog::submitImapAccount()
{
QString email = m_imapEmailEdit->text().trimmed();
@@ -489,20 +521,45 @@ void AccountSetupDialog::submitImapAccount()
if (smtpHost.isEmpty()) smtpHost = imapHost.replace("imap.", "smtp.");
if (name.isEmpty()) name = email.split("@").first();
// Create account with connection settings
Account account;
account.setEmail(email);
account.setDisplayName(name);
account.setType(AccountType::IMAP);
Account::ConnectionSettings settings;
settings.type = "imap";
settings.incomingHost = imapHost;
settings.incomingPort = imapPort.toInt();
settings.incomingSsl = m_sslCheckbox->isChecked();
settings.outgoingHost = smtpHost;
settings.outgoingPort = smtpPort.toInt();
settings.outgoingSsl = m_sslCheckbox->isChecked();
settings.username = email;
settings.password = password;
settings.authMethod = "plain";
account.setConnectionSettings(settings);
// Show progress
m_accountCreatedOk = false;
goToPage(PageProgress);
m_progressIcon->setText("");
m_progressText->setText("Conectando al servidor");
m_progressText->setText("Conectando al servidor...");
m_progressDetail->setText(
QString("Servidor IMAP: %1:%2\nServidor SMTP: %3:%4\nSSL: %5")
QString("Servidor IMAP: %1:%2\\nServidor SMTP: %3:%4\\nSSL: %5")
.arg(imapHost, imapPort, smtpHost, smtpPort,
m_sslCheckbox->isChecked() ? "" : "No")
);
// Simulate connection delay, then add account
QTimer::singleShot(1500, this, [this, email, name, password]() {
m_accountService->addAccount(email, "imap", password, "");
// Simulate connection delay, then add/update account
QTimer::singleShot(1500, this, [this, account]() mutable {
if (m_isEditing) {
account.setId(m_editingAccountId);
m_accountService->updateAccount(account);
} else {
m_accountService->addAccount(account);
}
});
// Timeout for IMAP too
@@ -510,21 +567,22 @@ void AccountSetupDialog::submitImapAccount()
}
// ─────────────────── Account Result ───────────────────
void AccountSetupDialog::onAccountAdded(const Account &account)
{
m_authTimeoutTimer->stop();
m_accountCreatedOk = true;
qDebug() << "[AccountSetupDialog] Account created successfully:" << account.email();
qDebug() << "[AccountSetupDialog] Account" << (m_isEditing ? "updated" : "created") << "successfully:" << account.email();
// If we were on the OAuth page, move to progress first
if (m_stack->currentIndex() == PageOAuth) {
goToPage(PageProgress);
}
showSuccess(QString("¡Cuenta «%1» configurada con éxito!\n\n"
"La sincronización de correos comenzará automáticamente en segundo plano.")
showSuccess(QString(m_isEditing ? "¡Cuenta «%1» actualizada con éxito!\\n\\n"
"Los cambios se aplicarán inmediatamente."
: "¡Cuenta «%1» configurada con éxito!\\n\\n"
"La sincronización de correos comenzará automáticamente en segundo plano.")
.arg(account.email()));
emit accountCreated(account);
@@ -551,4 +609,4 @@ void AccountSetupDialog::showError(const QString &message)
m_btnNext->setVisible(false);
}
#include "accountsetupdialog.moc"
#include "accountsetupdialog.moc"
+10 -1
View File
@@ -14,6 +14,8 @@
#include <QFormLayout>
#include <QIntValidator>
#include <QTimer>
#include <QRegularExpression>
#include <QRegularExpressionValidator>
#include "core/models/account.h"
#include "services/accountservice.h"
@@ -24,6 +26,9 @@ public:
explicit AccountSetupDialog(AccountService *accountService, QWidget *parent = nullptr);
~AccountSetupDialog() override = default;
/// Load an existing account for editing (currently only IMAP accounts supported)
void loadAccountForEditing(const Account &account);
signals:
void accountCreated(const Account &account);
@@ -91,6 +96,10 @@ private:
int m_selectedProvider; // 0=Gmail, 1=Outlook, 2=IMAP
bool m_accountCreatedOk;
// Editing state
int m_editingAccountId = -1;
bool m_isEditing = false;
};
#endif // ACCOUNTSETUPDIALOG_H
#endif // ACCOUNTSETUPDIALOG_H
+145 -18
View File
@@ -60,22 +60,22 @@ void RichTextEditor::setupToolbar(QVBoxLayout *layout) {
m_toolbar->addSeparator();
// Alignment
QAction *alignLeft = m_toolbar->addAction("\\xe2\\x87\\x94L");
QAction *alignLeft = m_toolbar->addAction("L");
connect(alignLeft, &QAction::triggered, this, &RichTextEditor::onAlignLeft);
QAction *alignCenter = m_toolbar->addAction("\\xe2\\x86\\x94C");
QAction *alignCenter = m_toolbar->addAction("C");
connect(alignCenter, &QAction::triggered, this, &RichTextEditor::onAlignCenter);
QAction *alignRight = m_toolbar->addAction("\\xe2\\x87\\x94R");
QAction *alignRight = m_toolbar->addAction("R");
connect(alignRight, &QAction::triggered, this, &RichTextEditor::onAlignRight);
QAction *alignJustify = m_toolbar->addAction("\\xe2\\x87\\x94J");
QAction *alignJustify = m_toolbar->addAction("J");
connect(alignJustify, &QAction::triggered, this, &RichTextEditor::onAlignJustify);
m_toolbar->addSeparator();
// Lists
QAction *bulletAct = m_toolbar->addAction("\\xe2\\x80\\xa2 List");
QAction *bulletAct = m_toolbar->addAction("Bullets");
connect(bulletAct, &QAction::triggered, this, &RichTextEditor::onBulletList);
QAction *numAct = m_toolbar->addAction("1. List");
@@ -84,23 +84,40 @@ void RichTextEditor::setupToolbar(QVBoxLayout *layout) {
m_toolbar->addSeparator();
// Indent / Outdent
QAction *indentAct = m_toolbar->addAction("\\xe2\\x86\\x92 Indent");
QAction *indentAct = m_toolbar->addAction("Indent");
connect(indentAct, &QAction::triggered, this, &RichTextEditor::onIndent);
QAction *outdentAct = m_toolbar->addAction("\\xe2\\x86\\x90 Outdent");
QAction *outdentAct = m_toolbar->addAction("Outdent");
connect(outdentAct, &QAction::triggered, this, &RichTextEditor::onOutdent);
m_toolbar->addSeparator();
// Insert image
QAction *imgAct = m_toolbar->addAction("\\xf0\\x9f\\x96\\xbc Image");
connect(imgAct, &QAction::triggered, this, &RichTextEditor::onInsertImage);
QAction *imgAct = m_toolbar->addAction("Img");
connect(imgAct, &QAction::triggered, this, &RichTextEditor::onInsertImage);
// Insert table
QAction *tableAct = m_toolbar->addAction("\\xe2\\x96\\xa4 Table");
connect(tableAct, &QAction::triggered, this, &RichTextEditor::onInsertTable);
// Insert table
QAction *tableAct = m_toolbar->addAction("Tbl");
connect(tableAct, &QAction::triggered, this, &RichTextEditor::onInsertTable);
layout->addWidget(m_toolbar);
// Signature button
m_signatureButton = new QToolButton();
m_signatureButton->setText("Signature");
m_signatureButton->setToolButtonStyle(Qt::ToolButtonTextOnly);
m_signatureButton->setPopupMode(QToolButton::MenuButtonPopup);
m_signatureMenu = new QMenu(m_signatureButton);
m_signatureButton->setMenu(m_signatureMenu);
m_signatureButton->setStyleSheet(
"QToolButton { background: #f5f5f7; border: 1px solid #d1d1d6; border-radius: 3px; padding: 4px 6px; }"
"QToolButton:hover { background: #e0e0e0; }"
"QToolButton::menu-button { border-left: 1px solid rgba(0,0,0,0.1); width: 12px; }"
);
connect(m_signatureButton, &QToolButton::clicked, this, &RichTextEditor::signatureClicked);
m_toolbar->addWidget(m_signatureButton);
m_toolbar->addSeparator();
layout->addWidget(m_toolbar);
}
void RichTextEditor::onBold() {
@@ -244,12 +261,99 @@ void RichTextEditor::onInsertTable() {
cursor.insertTable(rows, cols, tableFmt);
}
// ===================== ComposeView =====================
ComposeView::ComposeView(QWidget *parent) : QWidget(parent) {
setupUI();
// Connect the rich text editor's signature signal to our slot
connect(m_bodyEditor, &RichTextEditor::signatureClicked, this, &ComposeView::onSignatureClicked);
}
ComposeView::~ComposeView() {
// Destructor implementation
}
void ComposeView::onSignatureClicked() {
// This slot is called when the signature button in the rich text editor is clicked
// The actual menu handling is done in RichTextEditor, so we just need to
// handle any ComposeView-specific logic here if needed
}
void ComposeView::onSignatureEditRequested() {
// This slot will be connected to the "Edit Signature..." action in the menu
// For now, we'll just show a simple input dialog
QString currentSignature = "";
// In a real implementation, we would get the current signature for the selected account
// For now, we'll use an empty string
bool ok;
QString newSignature = QInputDialog::getMultiLineText(this, "Edit Signature",
"Enter your signature:",
currentSignature, &ok);
if (ok && !newSignature.isEmpty()) {
// In a real implementation, we would save this signature for the selected account
// For now, we'll just insert it at the cursor position
QTextCursor cursor = m_bodyEditor->textCursor();
cursor.insertHtml(newSignature);
}
}
void ComposeView::setAccountService(AccountService *service)
{
m_accountService = service;
populateAccountCombo();
}
void ComposeView::populateAccountCombo()
{
if (!m_accountService) {
qWarning() << "AccountService not set";
return;
}
m_accountCombo->clear();
m_accountCombo->addItem(tr("Select Account..."), QVariant());
QVector<Account> accounts = m_accountService->getAllAccounts();
for (const Account &account : accounts) {
m_accountCombo->addItem(account.email(), account.id());
}
// Select the first account by default if there are accounts
if (accounts.size() > 0) {
m_accountCombo->setCurrentIndex(1); // Skip the placeholder item
m_currentAccountId = accounts.first().id();
}
}
void ComposeView::onAccountChanged(int index)
{
if (index <= 0) {
// Placeholder item selected or invalid index
m_currentAccountId = -1;
return;
}
m_currentAccountId = m_accountCombo->itemData(index).toInt();
loadSignatureForCurrentAccount();
}
void ComposeView::loadSignatureForCurrentAccount()
{
if (!m_accountService || m_currentAccountId <= 0) {
// Clear signature if no account selected
m_bodyEditor->clear();
return;
}
Account* account = m_accountService->findAccountById(m_currentAccountId);
if (account) {
// For now, we'll just clear the editor since we don't have a signature field in Account yet
// In a real implementation, we would retrieve and display the signature
m_bodyEditor->clear();
} else {
m_bodyEditor->clear();
}
}
void ComposeView::setupUI() {
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(20, 15, 20, 15);
@@ -271,7 +375,7 @@ void ComposeView::setupUI() {
m_subjectField->setFont(subjectFont);
headerLayout->addWidget(m_subjectField, 1);
m_detachButton = new QPushButton("\\xe2\\x87\\xa5 Detach");
m_detachButton = new QPushButton("\xe2\x87\xa5 Detach");
m_detachButton->setToolTip("Open compose window in a separate window");
m_detachButton->setStyleSheet(
"QPushButton { background: transparent; border: 1px solid #d1d1d6; border-radius: 4px; padding: 6px 12px; color: #555; font-size: 12px; }"
@@ -288,6 +392,27 @@ void ComposeView::setupUI() {
line1->setStyleSheet("color: #e0e0e0;");
mainLayout->addWidget(line1);
// From: field + Account selector
QHBoxLayout *fromLayout = new QHBoxLayout();
QLabel *fromLabel = new QLabel("From:");
fromLabel->setFixedWidth(40);
fromLabel->setStyleSheet("font-weight: bold; color: #555;");
m_accountCombo = new QComboBox();
m_accountCombo->setFixedWidth(180);
m_accountCombo->setStyleSheet(
"QComboBox { border: 1px solid #d1d1d6; border-radius: 4px; padding: 6px 8px; "
"background: white; min-height: 20px; }"
"QComboBox:hover { border-color: #bdbdbd; }"
"QComboBox:focus { border-color: #1976D2; }"
"QComboBox::drop-down { border: none; width: 20px; }"
"QComboBox::down-arrow { image: url(:/icons/dropdown.png); width: 10px; height: 10px; }"
);
connect(m_accountCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &ComposeView::onAccountChanged);
fromLayout->addWidget(fromLabel);
fromLayout->addWidget(m_accountCombo, 1);
mainLayout->addLayout(fromLayout);
// To: field + Cc/Bcc toggle buttons
QHBoxLayout *toLayout = new QHBoxLayout();
QLabel *toLabel = new QLabel("To:");
@@ -462,7 +587,8 @@ void ComposeView::onSendClicked() {
m_bccVisible ? m_bccField->text() : QString(),
m_subjectField->text(),
m_bodyEditor->toHtml(),
QDateTime() // null = send now
QDateTime(), // null = send now
m_currentAccountId > 0 ? m_accountCombo->itemData(m_accountCombo->currentIndex()).toString() : QString()
);
}
@@ -473,7 +599,8 @@ void ComposeView::onScheduleClicked() {
m_bccVisible ? m_bccField->text() : QString(),
m_subjectField->text(),
m_bodyEditor->toHtml(),
m_schedulePicker->dateTime()
m_schedulePicker->dateTime(),
m_currentAccountId > 0 ? m_accountCombo->itemData(m_accountCombo->currentIndex()).toString() : QString()
);
}
+27 -4
View File
@@ -1,5 +1,5 @@
#ifndef COMPOSEVIEW_H
#define COMPOSEVIEW_H
#ifndef COMPOSE_VIEW_H
#define COMPOSE_VIEW_H
#include <QWidget>
#include <QTextEdit>
@@ -15,7 +15,11 @@
#include <QDateTimeEdit>
#include <QFontComboBox>
#include <QSpinBox>
#include <QComboBox>
#include "models/EmailCompositionModel.h"
#include "services/accountservice.h"
class AccountService;
class RichTextEditor : public QTextEdit {
Q_OBJECT
@@ -23,6 +27,10 @@ public:
explicit RichTextEditor(QWidget *parent = nullptr);
void setupToolbar(QVBoxLayout *layout);
signals:
void signatureClicked();
void signatureEditRequested();
public slots:
void onBold();
void onItalic();
@@ -44,19 +52,25 @@ private:
QToolBar *m_toolbar;
QFontComboBox *m_fontCombo;
QSpinBox *m_fontSizeSpin;
QToolButton *m_signatureButton;
QMenu *m_signatureMenu;
};
class ComposeView : public QWidget {
Q_OBJECT
public:
explicit ComposeView(QWidget *parent = nullptr);
~ComposeView() override;
void setAccountService(AccountService *service);
signals:
void compositionFinished();
void detachRequested(QWidget *widget);
void sendRequested(const QString &to, const QString &cc, const QString &bcc,
const QString &subject, const QString &body,
const QDateTime &scheduleTime);
const QDateTime &scheduleTime,
const QString &fromAddress);
void discardRequested();
public slots:
@@ -72,10 +86,17 @@ private slots:
void onSendClicked();
void onScheduleClicked();
void onDetachClicked();
void onAccountChanged(int index);
void onSignatureClicked();
void onSignatureEditRequested();
private:
void setupUI();
void populateAccountCombo();
void loadSignatureForCurrentAccount();
EmailCompositionModel *m_compositionModel;
AccountService *m_accountService = nullptr;
// UI components
QLineEdit *m_toField = nullptr;
QLineEdit *m_subjectField = nullptr;
@@ -97,8 +118,10 @@ private:
QAction *m_sendNowAction = nullptr;
QAction *m_scheduleAction = nullptr;
QToolButton *m_sendSplit = nullptr;
QComboBox *m_accountCombo = nullptr;
bool m_ccVisible = false;
bool m_bccVisible = false;
int m_currentAccountId = -1;
};
#endif // COMPOSEVIEW_H
#endif // COMPOSE_VIEW_H
+1
View File
@@ -18,6 +18,7 @@ public:
~MailListView() override = default;
void setModel(EmailListModel *model);
QTableView* tableView() const { return m_tableView; }
signals:
void emailSelected(int mailId);
+91 -21
View File
@@ -19,9 +19,20 @@ MainMainWindow::MainMainWindow(QWidget *parent)
connectModels();
setWindowTitle("Wino Mail DTK");
resize(1280, 820);
// Setup progress bar in status bar
m_progressBar = new QProgressBar(this);
m_progressBar->setMaximumWidth(120);
m_progressBar->setVisible(false);
statusBar()->addPermanentWidget(m_progressBar);
// Connect mail service progress/status signals
connect(m_mailService, &MailService::progressChanged, this, &MainMainWindow::onProgressChanged);
connect(m_mailService, &MailService::statusMessage, this, &MainMainWindow::onStatusMessage);
// Provide account service to settings view
m_settingsView->setAccountService(m_accountService);
}
void MainMainWindow::setupUI() {
void MainMainWindow::setupUI()
{
// === Global stylesheet ===
this->setStyleSheet(
"QMainWindow { background-color: #f5f5f7; }"
@@ -58,7 +69,7 @@ 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) {
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) {
QString msg;
if (scheduleTime.isValid()) {
msg = QString("Message scheduled for: %1").arg(scheduleTime.toString("dd/MM/yyyy hh:mm AP"));
@@ -83,16 +94,16 @@ void MainMainWindow::setupUI() {
// Create standalone window
QMainWindow *detachedWin = new QMainWindow();
detachedWin->setWindowTitle("Compose - Wino Mail");
// Assign central widget and ensure it's visible and sized
detachedWin->setCentralWidget(composeView);
composeView->setMinimumSize(800, 600);
composeView->update();
detachedWin->resize(800, 600);
detachedWin->setAttribute(Qt::WA_DeleteOnClose);
detachedWin->show();
statusBar()->showMessage("Compose view detached to separate window", 3000);
});
m_stack->addWidget(m_composeView);
@@ -103,6 +114,8 @@ void MainMainWindow::setupUI() {
AccountSetupDialog dlg(m_accountService, this);
dlg.exec();
});
connect(m_settingsView, &SettingsView::accountEditRequested, this, &MainMainWindow::onAccountEditRequested);
connect(m_settingsView, &SettingsView::accountDeleteRequested, this, &MainMainWindow::onAccountDeleteRequested);
connect(m_settingsView, &SettingsView::themeChanged, [this](const QString &theme) {
statusBar()->showMessage(QString("Theme changed to: %1 (restart may be required)").arg(theme), 3000);
});
@@ -123,7 +136,8 @@ void MainMainWindow::setupUI() {
switchToPage(PageMail);
}
void MainMainWindow::setupSidebar() {
void MainMainWindow::setupSidebar()
{
m_sidebar = new QListWidget();
m_sidebar->setFixedWidth(64);
m_sidebar->setIconSize(QSize(24, 24));
@@ -146,7 +160,8 @@ void MainMainWindow::setupSidebar() {
connect(m_sidebar, &QListWidget::currentRowChanged, this, &MainMainWindow::onNavChanged);
}
void MainMainWindow::setupMailPage() {
void MainMainWindow::setupMailPage()
{
m_mailPage = new QWidget();
QHBoxLayout *mailLayout = new QHBoxLayout(m_mailPage);
mailLayout->setContentsMargins(0, 0, 0, 0);
@@ -191,9 +206,11 @@ void MainMainWindow::setupMailPage() {
mailLayout->addWidget(m_folderSplitter);
}
void MainMainWindow::connectModels() {
void MainMainWindow::connectModels()
{
m_accountService = new AccountService(this);
m_mailService = new MailService(this);
m_mailService = new MailService(m_accountService, this);
m_composeView->setAccountService(m_accountService);
m_folderModel = new FolderListModel(m_accountService, this);
m_emailModel = new EmailListModel(this);
@@ -206,11 +223,13 @@ void MainMainWindow::connectModels() {
connect(m_folderTree, &QTreeView::clicked, this, &MainMainWindow::onFolderSelected);
}
void MainMainWindow::onNavChanged(int index) {
void MainMainWindow::onNavChanged(int index)
{
switchToPage(static_cast<Page>(index));
}
void MainMainWindow::switchToPage(int pageIndex) {
void MainMainWindow::switchToPage(int pageIndex)
{
m_stack->setCurrentIndex(pageIndex);
m_sidebar->blockSignals(true);
m_sidebar->setCurrentRow(pageIndex);
@@ -219,7 +238,8 @@ void MainMainWindow::switchToPage(int pageIndex) {
// Show/hide toolbar actions per page
}
void MainMainWindow::onFolderSelected(const QModelIndex &index) {
void MainMainWindow::onFolderSelected(const QModelIndex &index)
{
if (!index.isValid()) return;
int itemType = index.data(FolderListModel::ItemTypeRole).toInt();
@@ -240,7 +260,8 @@ void MainMainWindow::onFolderSelected(const QModelIndex &index) {
}
}
void MainMainWindow::onEmailSelected(int mailId) {
void MainMainWindow::onEmailSelected(int mailId)
{
m_currentMailId = mailId;
std::optional<MailItem> item = MailItemDao::findById(mailId);
if (!item.has_value()) {
@@ -256,11 +277,13 @@ void MainMainWindow::onEmailSelected(int mailId) {
m_emailViewer->setMailItem(&mail);
}
void MainMainWindow::onComposeRequested() {
void MainMainWindow::onComposeRequested()
{
switchToPage(PageCompose);
}
void MainMainWindow::onReaderReplyRequested(const MailItem *item) {
void MainMainWindow::onReaderReplyRequested(const MailItem *item)
{
if (item) {
m_composeView->setTo(item->sender());
m_composeView->setSubject("Re: " + item->subject());
@@ -268,29 +291,32 @@ void MainMainWindow::onReaderReplyRequested(const MailItem *item) {
switchToPage(PageCompose);
}
void MainMainWindow::onNewMessage() {
void MainMainWindow::onNewMessage()
{
switchToPage(PageCompose);
}
void MainMainWindow::openMailInIndependentWindow(int mailId) {
void MainMainWindow::openMailInIndependentWindow(int mailId)
{
std::optional<MailItem> item = MailItemDao::findById(mailId);
if (!item.has_value()) return;
QMainWindow *detachedWin = new QMainWindow();
detachedWin->setWindowTitle(QString("Mail - %1").arg(item->subject()));
ReaderView *detachedReader = new ReaderView();
detachedReader->setMailItem(&item.value());
detachedWin->setCentralWidget(detachedReader);
detachedWin->resize(800, 600);
detachedWin->setAttribute(Qt::WA_DeleteOnClose);
detachedWin->show();
statusBar()->showMessage("Opened mail in independent window", 3000);
}
void MainMainWindow::createToolBar() {
void MainMainWindow::createToolBar()
{
m_toolBar = addToolBar("Main Toolbar");
m_toolBar->setMovable(false);
@@ -317,4 +343,48 @@ void MainMainWindow::createToolBar() {
connect(deleteAction, &QAction::triggered, [this]() {
statusBar()->showMessage("Delete would be implemented here", 3000);
});
}
void MainMainWindow::onProgressChanged(int percent)
{
if (percent < 0 || percent > 100) {
m_progressBar->setVisible(false);
return;
}
m_progressBar->setValue(percent);
m_progressBar->setVisible(true);
}
void MainMainWindow::onStatusMessage(const QString &msg)
{
// Show temporary message in status bar (timeout 5000 ms)
statusBar()->showMessage(msg, 5000);
}
void MainMainWindow::onAddAccountRequested()
{
AccountSetupDialog *dialog = new AccountSetupDialog(m_accountService, this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowFlag(Qt::Window, true); // make it an independent window
dialog->show();
}
void MainMainWindow::onAccountEditRequested(int accountId)
{
QMessageBox::information(this, "Debug", QString("Edit account slot called for ID %1").arg(accountId));
Account* account = m_accountService->findAccountById(accountId);
if (!account) {
statusBar()->showMessage(QString("Error: account %1 not found").arg(accountId), 3000);
return;
}
AccountSetupDialog dlg(m_accountService, this);
dlg.loadAccountForEditing(*account);
dlg.exec();
delete account;
}
void MainMainWindow::onAccountDeleteRequested(int accountId)
{
Q_UNUSED(accountId);
statusBar()->showMessage(QString("Delete account %1 requested").arg(accountId), 3000);
}
+8
View File
@@ -6,6 +6,7 @@
#include <QSplitter>
#include <QTreeView>
#include <QStatusBar>
#include <QProgressBar>
#include <QToolBar>
#include <QAction>
@@ -35,6 +36,11 @@ private slots:
void onReaderReplyRequested(const MailItem *item);
void onNewMessage();
void openMailInIndependentWindow(int mailId);
void onAddAccountRequested();
void onAccountEditRequested(int accountId);
void onAccountDeleteRequested(int accountId);
void onProgressChanged(int percent);
void onStatusMessage(const QString &msg);
private:
void setupUI();
@@ -78,4 +84,6 @@ private:
QToolBar *m_toolBar;
int m_currentFolderId;
int m_currentMailId;
QProgressBar *m_progressBar;
};
+36 -35
View File
@@ -43,7 +43,7 @@ int FolderTreeItem::row() const
return 0;
}
FolderTreeItem *FolderTreeItem::parentItem()
FolderTreeItem *FolderTreeItem::parentItem() const
{
return m_parentItem;
}
@@ -53,9 +53,14 @@ FolderTreeItem *FolderTreeItem::parentItem()
FolderListModel::FolderListModel(AccountService *accountService, QObject *parent)
: QAbstractItemModel(parent),
m_accountService(accountService),
m_rootItem(new FolderTreeItem(FolderTreeItem::AccountNode, "Root"))
m_rootItem(new FolderTreeItem(FolderTreeItem::AccountNode, QStringLiteral("Root")))
{
refresh();
// Connect to account list changes
if (m_accountService) {
connect(m_accountService, &AccountService::accountListChanged,
this, &FolderListModel::onAccountListChanged);
}
}
FolderListModel::~FolderListModel()
@@ -151,27 +156,38 @@ QVariant FolderListModel::data(const QModelIndex &index, int role) const
if (role == UnreadCountRole) {
if (item->type() == FolderTreeItem::FolderNode) {
Folder folder = item->data().value<Folder>();
return folder.unreadCount();
// TODO: compute unread count
return 0;
}
}
return QVariant();
}
bool FolderListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
Q_UNUSED(index);
Q_UNUSED(value);
Q_UNUSED(role);
return false;
}
Qt::ItemFlags FolderListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
QHash<int, QByteArray> FolderListModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[Qt::DisplayRole] = "display";
roles[NameRole] = "name";
roles[ItemTypeRole] = "itemType";
roles[AccountIdRole] = "accountId";
roles[FolderIdRole] = "folderId";
roles[NameRole] = "name";
roles[UnreadCountRole] = "unreadCount";
return roles;
}
@@ -182,44 +198,31 @@ void FolderListModel::refresh()
clearModel();
setupModelData();
endResetModel();
qDebug() << "FolderListModel refreshed";
}
void FolderListModel::onAccountListChanged()
{
refresh();
}
void FolderListModel::setupModelData()
{
if (!m_accountService)
return;
QVector<Account> accounts = m_accountService->getAllAccounts();
// If no accounts exist yet, add one sample account with default folders
if (accounts.isEmpty()) {
Account sample(1, "javi@example.com", "Javi's Email",
AccountType::IMAP);
accounts.append(sample);
}
for (const Account &acc : accounts) {
QVariant accVariant;
accVariant.setValue(acc);
FolderTreeItem *accountItem = new FolderTreeItem(FolderTreeItem::AccountNode, accVariant, m_rootItem);
QVariant accData;
accData.setValue(acc);
FolderTreeItem *accountItem = new FolderTreeItem(FolderTreeItem::AccountNode, accData, m_rootItem);
m_rootItem->appendChild(accountItem);
// Get folders for this account
// Load folders for this account
QVector<Folder> folders = FolderDao::findByAccountId(acc.id());
// If no folders exist yet, create default ones
if (folders.isEmpty()) {
QStringList defaultFolders = {"Inbox", "Sent", "Drafts", "Trash", "Spam"};
for (const QString &name : defaultFolders) {
Folder f;
f.setName(name);
f.setAccountId(acc.id());
folders.append(f);
}
}
for (const Folder &folder : folders) {
QVariant folderVariant;
folderVariant.setValue(folder);
FolderTreeItem *folderItem = new FolderTreeItem(FolderTreeItem::FolderNode, folderVariant, accountItem);
QVariant folderData;
folderData.setValue(folder);
FolderTreeItem *folderItem = new FolderTreeItem(FolderTreeItem::FolderNode, folderData, accountItem);
accountItem->appendChild(folderItem);
}
}
@@ -228,6 +231,4 @@ void FolderListModel::setupModelData()
void FolderListModel::clearModel()
{
m_rootItem->clearChildren();
}
#include "FolderListModel.moc"
}
+7 -3
View File
@@ -23,7 +23,7 @@ public:
FolderTreeItem *child(int row);
int childCount() const;
int row() const;
FolderTreeItem *parentItem();
FolderTreeItem *parentItem() const;
Type type() const { return m_type; }
QVariant data() const { return m_data; }
@@ -54,17 +54,21 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QHash<int, QByteArray> roleNames() const override;
public slots:
void refresh();
private slots:
void onAccountListChanged();
private:
void setupModelData();
void clearModel();
FolderTreeItem *m_rootItem;
AccountService *m_accountService;
FolderTreeItem *m_rootItem;
};
#endif // FOLDERLISTMODEL_H
+156 -76
View File
@@ -2,6 +2,18 @@
#include "services/accountservice.h"
#include "core/models/account.h"
#include <QDebug>
#include <QMessageBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QListWidget>
#include <QPushButton>
#include <QLabel>
#include <QFont>
#include <QButtonGroup>
#include <QComboBox>
#include <QCheckBox>
SettingsView::SettingsView(QWidget *parent) : QWidget(parent) {
setupUI();
}
@@ -19,124 +31,128 @@ void SettingsView::setupUI() {
}
QWidget* SettingsView::createAccountsTab() {
QWidget *w = new QWidget();
QVBoxLayout *layout = new QVBoxLayout(w);
QWidget *widget = new QWidget();
QVBoxLayout *layout = new QVBoxLayout(widget);
layout->setContentsMargins(20, 20, 20, 20);
layout->setSpacing(15);
QLabel *sectionTitle = new QLabel("Email Accounts");
QFont titleFont = sectionTitle->font();
QLabel *title = new QLabel("Email Accounts");
QFont titleFont = title->font();
titleFont.setPointSize(16);
titleFont.setBold(true);
sectionTitle->setFont(titleFont);
layout->addWidget(sectionTitle);
title->setFont(titleFont);
layout->addWidget(title);
m_accountList = new QListWidget();
m_accountList->setAlternatingRowColors(true);
m_accountList->setFrameShape(QFrame::NoFrame);
m_accountList->setSelectionMode(QAbstractItemView::SingleSelection);
m_accountList->setStyleSheet(
"QListWidget { background: #ffffff; border: 1px solid #e0e0e0; border-radius: 4px; }"
"QListWidget::item { padding: 12px; border-bottom: 1px solid #f0f0f0; }"
"QListWidget::item:selected { background: #e3f2fd; }"
);
layout->addWidget(m_accountList, 1);
layout->addWidget(m_accountList);
QPushButton *addBtn = new QPushButton("+ Add Email Account");
addBtn->setStyleSheet(
"QPushButton { background: #1976D2; color: white; border: none; border-radius: 4px; padding: 10px 20px; font-weight: bold; }"
"QPushButton:hover { background: #1565C0; }"
QHBoxLayout *buttonLayout = new QHBoxLayout();
m_addBtn = new QPushButton("Add Account");
m_addBtn->setStyleSheet(
"QPushButton { background: #FFEB3B; color: white; border: none; border-radius: 4px; "
"padding: 8px 16px; font-weight: bold; }"
"QPushButton:hover { background: #FDD835; }"
);
connect(addBtn, &QPushButton::clicked, this, &SettingsView::accountAddRequested);
layout->addWidget(addBtn);
buttonLayout->addWidget(m_addBtn);
m_editBtn = new QPushButton("Edit");
m_editBtn->setEnabled(false);
m_editBtn->setStyleSheet(
"QPushButton { background: #FFA726; color: white; border: none; border-radius: 4px; "
"padding: 8px 16px; font-weight: bold; }"
"QPushButton:hover { background: #FB8C00; }"
);
m_deleteBtn = new QPushButton("Delete");
m_deleteBtn->setEnabled(false);
m_deleteBtn->setStyleSheet(
"QPushButton { background: #EF5350; color: white; border: none; border-radius: 4px; "
"padding: 8px 16px; font-weight: bold; }"
"QPushButton:hover { background: #E53935; }"
);
buttonLayout->addWidget(m_editBtn);
buttonLayout->addWidget(m_deleteBtn);
buttonLayout->addStretch();
layout->addLayout(buttonLayout);
return w;
connect(m_accountList, &QListWidget::itemSelectionChanged, this, &SettingsView::onAccountSelectionChanged);
connect(m_editBtn, &QPushButton::clicked, this, &SettingsView::onEditClicked);
connect(m_deleteBtn, &QPushButton::clicked, this, &SettingsView::onDeleteClicked);
connect(m_addBtn, &QPushButton::clicked, this, &SettingsView::accountAddRequested);
return widget;
}
QWidget* SettingsView::createGeneralTab() {
QWidget *w = new QWidget();
QVBoxLayout *layout = new QVBoxLayout(w);
QWidget *widget = new QWidget();
QVBoxLayout *layout = new QVBoxLayout(widget);
layout->setContentsMargins(20, 20, 20, 20);
layout->setSpacing(15);
QLabel *sectionTitle = new QLabel("General Settings");
QFont titleFont = sectionTitle->font();
QLabel *title = new QLabel("General Settings");
QFont titleFont = title->font();
titleFont.setPointSize(16);
titleFont.setBold(true);
sectionTitle->setFont(titleFont);
layout->addWidget(sectionTitle);
title->setFont(titleFont);
layout->addWidget(title);
QCheckBox *startOnLogin = new QCheckBox("Start application on login");
startOnLogin->setChecked(true);
connect(startOnLogin, &QCheckBox::toggled, [this](bool checked) {
emit settingChanged("start_on_login", checked);
connect(startOnLogin, &QCheckBox::toggled, this, [this](bool checked) {
emit settingChanged("start_on_login", QVariant(checked));
});
layout->addWidget(startOnLogin);
QCheckBox *enableNotifications = new QCheckBox("Enable notifications");
enableNotifications->setChecked(true);
connect(enableNotifications, &QCheckBox::toggled, [this](bool checked) {
emit settingChanged("notifications_enabled", checked);
connect(enableNotifications, &QCheckBox::toggled, this, [this](bool checked) {
emit settingChanged("enable_notifications", QVariant(checked));
});
layout->addWidget(enableNotifications);
QCheckBox *minimizeToTray = new QCheckBox("Minimize to tray on close");
minimizeToTray->setChecked(true);
connect(minimizeToTray, &QCheckBox::toggled, [this](bool checked) {
emit settingChanged("minimize_to_tray", checked);
connect(minimizeToTray, &QCheckBox::toggled, this, [this](bool checked) {
emit settingChanged("minimize_to_tray", QVariant(checked));
});
layout->addWidget(minimizeToTray);
layout->addSpacing(10);
QHBoxLayout *syncLayout = new QHBoxLayout();
QLabel *syncLabel = new QLabel("Sync Interval:");
syncLabel->setStyleSheet("font-weight: bold; color: #555;");
m_syncIntervalCombo = new QComboBox();
m_syncIntervalCombo->addItem("15 minutes", 15);
m_syncIntervalCombo->addItem("30 minutes", 30);
m_syncIntervalCombo->addItem("60 minutes", 60);
m_syncIntervalCombo->addItem("120 minutes", 120);
m_syncIntervalCombo->setCurrentIndex(1);
connect(m_syncIntervalCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int idx) {
emit settingChanged("sync_interval", m_syncIntervalCombo->itemData(idx));
});
syncLayout->addWidget(syncLabel);
syncLayout->addWidget(m_syncIntervalCombo);
syncLayout->addStretch();
layout->addLayout(syncLayout);
layout->addStretch();
return w;
return widget;
}
QWidget* SettingsView::createAppearanceTab() {
QWidget *w = new QWidget();
QVBoxLayout *layout = new QVBoxLayout(w);
QWidget *widget = new QWidget();
QVBoxLayout *layout = new QVBoxLayout(widget);
layout->setContentsMargins(20, 20, 20, 20);
layout->setSpacing(15);
QLabel *sectionTitle = new QLabel("Appearance");
QFont titleFont = sectionTitle->font();
QLabel *title = new QLabel("Appearance");
QFont titleFont = title->font();
titleFont.setPointSize(16);
titleFont.setBold(true);
sectionTitle->setFont(titleFont);
layout->addWidget(sectionTitle);
title->setFont(titleFont);
layout->addWidget(title);
QLabel *themeLabel = new QLabel("Theme:");
themeLabel->setStyleSheet("font-weight: bold; color: #555;");
QLabel *themeLabel = new QLabel("Theme");
QFont labelFont = themeLabel->font();
labelFont.setBold(true);
themeLabel->setFont(labelFont);
layout->addWidget(themeLabel);
m_themeGroup = new QButtonGroup(this);
QRadioButton *lightRadio = new QRadioButton("Light");
QRadioButton *darkRadio = new QRadioButton("Dark");
QRadioButton *systemRadio = new QRadioButton("System");
lightRadio->setChecked(true);
m_themeGroup->addButton(lightRadio, 0);
m_themeGroup->addButton(darkRadio, 1);
m_themeGroup->addButton(systemRadio, 2);
connect(m_themeGroup, QOverload<int>::of(&QButtonGroup::idClicked), [this](int id) {
QHBoxLayout *themeLayout = new QHBoxLayout();
themeLayout->addWidget(lightRadio);
themeLayout->addWidget(darkRadio);
themeLayout->addWidget(systemRadio);
layout->addLayout(themeLayout);
connect(m_themeGroup, QOverload<int>::of(&QButtonGroup::idClicked), this, [this](int id) {
QString theme;
switch (id) {
case 0: theme = "light"; break;
@@ -146,19 +162,83 @@ QWidget* SettingsView::createAppearanceTab() {
emit themeChanged(theme);
});
layout->addWidget(lightRadio);
layout->addWidget(darkRadio);
layout->addWidget(systemRadio);
layout->addSpacing(15);
QCheckBox *deepinTheme = new QCheckBox("Use Deepin theme");
QCheckBox *deepinTheme = new QCheckBox("Deepin theme (experimental)");
deepinTheme->setChecked(true);
connect(deepinTheme, &QCheckBox::toggled, [this](bool checked) {
emit settingChanged("deepin_theme", checked);
connect(deepinTheme, &QCheckBox::toggled, this, [this](bool checked) {
emit settingChanged("deepin_theme", QVariant(checked));
});
layout->addWidget(deepinTheme);
layout->addStretch();
return w;
}
return widget;
}
void SettingsView::setAccountService(AccountService *service) {
m_accountService = service;
if (m_accountService) {
loadAccounts();
connect(m_accountService, &AccountService::accountListChanged, this, &SettingsView::onAccountListChanged);
}
}
void SettingsView::onAccountListChanged() {
loadAccounts();
}
void SettingsView::loadAccounts() {
if (!m_accountService) {
qDebug() << "AccountService not set";
return;
}
m_accountList->clear();
QList<Account> accounts = m_accountService->getAllAccounts();
qDebug() << "Loaded" << accounts.size() << "accounts from AccountService";
for (const Account &acc : accounts) {
QString display = acc.displayName().isEmpty() ? acc.email() : acc.displayName();
QString text = QString("%1 (%2)").arg(acc.email(), display);
QListWidgetItem *item = new QListWidgetItem(text, m_accountList);
item->setData(Qt::UserRole, acc.id());
m_accountList->addItem(item);
}
if (m_accountList->count() > 0) {
m_accountList->setCurrentRow(0);
}
}
void SettingsView::onAccountSelectionChanged()
{
QList<QListWidgetItem*> items = m_accountList->selectedItems();
qDebug() << "SettingsView::onAccountSelectionChanged, selected items count:" << items.size();
if (items.isEmpty()) {
m_selectedAccountId = -1;
m_editBtn->setEnabled(false);
m_deleteBtn->setEnabled(false);
return;
}
QListWidgetItem *item = items.first();
m_selectedAccountId = item->data(Qt::UserRole).toInt();
qDebug() << "SettingsView::onAccountSelectionChanged, selected account id:" << m_selectedAccountId;
m_editBtn->setEnabled(true);
m_deleteBtn->setEnabled(true);
}
void SettingsView::onEditClicked() {
qDebug() << "SettingsView::onEditClicked called, selected account id:" << m_selectedAccountId;
if (m_selectedAccountId != -1 && m_accountService) {
emit accountEditRequested(m_selectedAccountId);
}
}
void SettingsView::onDeleteClicked() {
if (m_selectedAccountId != -1 && m_accountService) {
QMessageBox msgBox(this);
msgBox.setWindowTitle("Delete Account");
Account* account = m_accountService->findAccountById(m_selectedAccountId);
QString accountName = account ? account->email() : QString::number(m_selectedAccountId);
msgBox.setText(QString("Are you sure you want to delete the account \"%1\"?").arg(accountName));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
if (msgBox.exec() == QMessageBox::Yes) {
emit accountDeleteRequested(m_selectedAccountId);
}
}
}
+18 -2
View File
@@ -12,6 +12,7 @@
#include <QRadioButton>
#include <QButtonGroup>
#include <QVariant>
#include "services/accountservice.h"
class SettingsView : public QWidget {
Q_OBJECT
@@ -20,13 +21,22 @@ public:
explicit SettingsView(QWidget *parent = nullptr);
~SettingsView() override = default;
void setAccountService(AccountService *service);
signals:
void accountAddRequested();
void accountEditRequested(int accountIndex);
void accountDeleteRequested(int accountIndex);
void accountEditRequested(int accountId);
void accountDeleteRequested(int accountId);
void themeChanged(const QString &theme);
void settingChanged(const QString &key, const QVariant &value);
private slots:
void onAccountListChanged();
void loadAccounts();
void onEditClicked();
void onDeleteClicked();
void onAccountSelectionChanged();
private:
void setupUI();
QWidget* createAccountsTab();
@@ -37,4 +47,10 @@ private:
QListWidget *m_accountList;
QComboBox *m_syncIntervalCombo;
QButtonGroup *m_themeGroup;
QPushButton *m_editBtn;
QPushButton *m_addBtn;
QPushButton *m_deleteBtn;
AccountService *m_accountService;
int m_selectedAccountId;
};