Actualizaciones varias: mejoras en cuenta, sincronización, UI
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "accountsetupdialoglauncher.h"
|
||||
#include <QDebug>
|
||||
#include "services/accountservice.h"
|
||||
|
||||
AccountSetupDialogLauncher::AccountSetupDialogLauncher(QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -52,6 +53,9 @@ void AccountSetupDialogLauncher::initializeSynchronizer(const Account &account)
|
||||
case AccountType::IMAP:
|
||||
providerType = "imap";
|
||||
break;
|
||||
case AccountType::POP3:
|
||||
providerType = "pop3";
|
||||
break;
|
||||
default:
|
||||
providerType = "imap";
|
||||
break;
|
||||
|
||||
@@ -6,7 +6,8 @@ MailItem::MailItem(qint64 id, int folderId, const QString& subject, const QStrin
|
||||
const QVector<QString>& attachments,
|
||||
const QString& fileId,
|
||||
qint64 size,
|
||||
const QString& messageId)
|
||||
const QString& messageId,
|
||||
qint64 uid)
|
||||
: m_id(id)
|
||||
, m_folderId(folderId)
|
||||
, m_subject(subject)
|
||||
@@ -19,5 +20,6 @@ MailItem::MailItem(qint64 id, int folderId, const QString& subject, const QStrin
|
||||
, m_fileId(fileId)
|
||||
, m_size(size)
|
||||
, m_messageId(messageId)
|
||||
, m_uid(uid)
|
||||
{
|
||||
}
|
||||
+11
-6
@@ -14,7 +14,8 @@ public:
|
||||
const QVector<QString>& attachments = QVector<QString>(),
|
||||
const QString& fileId = QString(),
|
||||
qint64 size = 0,
|
||||
const QString& messageId = QString());
|
||||
const QString& messageId = QString(),
|
||||
qint64 uid = 0);
|
||||
|
||||
qint64 id() const { return m_id; }
|
||||
void setId(qint64 id) { m_id = id; }
|
||||
@@ -51,16 +52,19 @@ public:
|
||||
|
||||
QString messageId() const { return m_messageId; }
|
||||
void setMessageId(const QString& messageId) { m_messageId = messageId; }
|
||||
|
||||
QString bodyHtml() const { return m_bodyHtml; }
|
||||
|
||||
qint64 uid() const { return m_uid; }
|
||||
void setUid(qint64 uid) { m_uid = uid; }
|
||||
|
||||
QString bodyHtml() const { return m_bodyHtml; }
|
||||
void setBodyHtml(const QString& body) { m_bodyHtml = body; }
|
||||
|
||||
|
||||
QString to() const { return m_to; }
|
||||
void setTo(const QString& to) { m_to = to; }
|
||||
|
||||
|
||||
QString cc() const { return m_cc; }
|
||||
void setCc(const QString& cc) { m_cc = cc; }
|
||||
|
||||
|
||||
QString bcc() const { return m_bcc; }
|
||||
void setBcc(const QString& bcc) { m_bcc = bcc; }
|
||||
|
||||
@@ -77,6 +81,7 @@ private:
|
||||
QString m_fileId;
|
||||
qint64 m_size{0};
|
||||
QString m_messageId;
|
||||
qint64 m_uid{0};
|
||||
QString m_bodyHtml;
|
||||
QString m_to;
|
||||
QString m_cc;
|
||||
|
||||
@@ -1,14 +1,44 @@
|
||||
#include "account.h"
|
||||
|
||||
Account::Account(int id, const QString& email, const QString& displayName,
|
||||
const QString& signature,
|
||||
AccountType type, const QString& accessToken,
|
||||
const QString& refreshToken, const QDateTime& tokenExpires)
|
||||
const QString& refreshToken,
|
||||
const QDateTime& tokenExpires)
|
||||
: m_id(id)
|
||||
, m_email(email)
|
||||
, m_displayName(displayName)
|
||||
, m_signature(signature)
|
||||
, m_type(type)
|
||||
, m_accessToken(accessToken)
|
||||
, m_refreshToken(refreshToken)
|
||||
, m_tokenExpires(tokenExpires)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
QString Account::encryptPassword(const QString& plainText)
|
||||
{
|
||||
// Simple XOR encryption with a fixed key, then base64 encode
|
||||
// Note: This is for obfuscation only, not strong security.
|
||||
static const char keyHex[] = "0123456789ABCDEF"; // 16-byte key
|
||||
QByteArray key;
|
||||
bool ok;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
char c = QString(keyHex + i * 2, 2).toInt(&ok, 16);
|
||||
if (!ok) c = 0;
|
||||
key.append(c);
|
||||
}
|
||||
|
||||
QByteArray input = plainText.toUtf8();
|
||||
QByteArray result(input.size(), 0);
|
||||
for (int i = 0; i < input.size(); ++i) {
|
||||
result[i] = input[i] ^ key[i % key.size()];
|
||||
}
|
||||
return result.toBase64();
|
||||
}
|
||||
|
||||
QString Account::decryptPassword(const QString& encrypted)
|
||||
{
|
||||
// Same as encryption since XOR is symmetric
|
||||
return encryptPassword(encrypted);
|
||||
}
|
||||
|
||||
@@ -2,18 +2,21 @@
|
||||
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <QtGlobal>
|
||||
#include <QByteArray>
|
||||
|
||||
enum class AccountType {
|
||||
POP3,
|
||||
Outlook,
|
||||
Gmail,
|
||||
IMAP
|
||||
};
|
||||
|
||||
class Account
|
||||
{
|
||||
class Account {
|
||||
public:
|
||||
Account() = default;
|
||||
Account(int id, const QString& email, const QString& displayName,
|
||||
const QString& signature,
|
||||
AccountType type, const QString& accessToken = QString(),
|
||||
const QString& refreshToken = QString(),
|
||||
const QDateTime& tokenExpires = QDateTime());
|
||||
@@ -27,6 +30,9 @@ public:
|
||||
QString displayName() const { return m_displayName; }
|
||||
void setDisplayName(const QString& displayName) { m_displayName = displayName; }
|
||||
|
||||
QString signature() const { return m_signature; }
|
||||
void setSignature(const QString& signature) { m_signature = signature; }
|
||||
|
||||
AccountType type() const { return m_type; }
|
||||
void setType(AccountType type) { m_type = type; }
|
||||
|
||||
@@ -44,12 +50,35 @@ public:
|
||||
m_tokenExpires > QDateTime::currentDateTimeUtc();
|
||||
}
|
||||
|
||||
/// Connection settings for incoming and outgoing servers
|
||||
struct ConnectionSettings {
|
||||
QString type; // "imap" or "pop3"
|
||||
QString incomingHost;
|
||||
quint16 incomingPort = 0;
|
||||
bool incomingSsl = false;
|
||||
QString outgoingHost;
|
||||
quint16 outgoingPort = 0;
|
||||
bool outgoingSsl = false;
|
||||
QString username;
|
||||
QString password;
|
||||
QString authMethod; // "plain" or "oauth2"
|
||||
};
|
||||
|
||||
const ConnectionSettings& connectionSettings() const { return m_connectionSettings; }
|
||||
void setConnectionSettings(const ConnectionSettings& settings) { m_connectionSettings = settings; }
|
||||
|
||||
/// Simple encryption/decryption for passwords (XOR with fixed key, base64 encoded)
|
||||
static QString encryptPassword(const QString& plainText);
|
||||
static QString decryptPassword(const QString& encrypted);
|
||||
|
||||
private:
|
||||
int m_id{0};
|
||||
QString m_email;
|
||||
QString m_displayName;
|
||||
QString m_signature;
|
||||
AccountType m_type{AccountType::IMAP};
|
||||
QString m_accessToken;
|
||||
QString m_refreshToken;
|
||||
QDateTime m_tokenExpires;
|
||||
};
|
||||
ConnectionSettings m_connectionSettings;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "services/gmail/gmailsynchronizer.h"
|
||||
#include "services/outlook/outlooksynchronizer.h"
|
||||
#include "services/imap/imapsynchronizer.h"
|
||||
#include "services/pop3/pop3synchronizer.h"
|
||||
#include <QDebug>
|
||||
|
||||
SynchronizerProvider::SynchronizerProvider(QObject *parent)
|
||||
@@ -52,6 +53,8 @@ Synchronizer* SynchronizerProvider::createSynchronizer(const QString &accountId,
|
||||
sync = new OutlookSynchronizer(this);
|
||||
} else if (providerType == "imap") {
|
||||
sync = new ImapSynchronizer(this);
|
||||
} else if (providerType == "pop3" || providerType == "pop") {
|
||||
sync = new Pop3Synchronizer(this);
|
||||
} else {
|
||||
qWarning() << "[SynchronizerProvider] Unknown provider type:" << providerType;
|
||||
return nullptr;
|
||||
|
||||
+108
-30
@@ -1,28 +1,40 @@
|
||||
#include "accountdao.h"
|
||||
|
||||
bool AccountDao::insert(const Account& account)
|
||||
qint64 AccountDao::insert(const Account& account)
|
||||
{
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"INSERT INTO Account (email, displayName, type, accessToken, refreshToken, tokenExpires) "
|
||||
"VALUES (:email, :displayName, :type, :accessToken, :refreshToken, :tokenExpires)"
|
||||
);
|
||||
"INSERT INTO Account (email, displayName, signature, type, accessToken, refreshToken, tokenExpires, "
|
||||
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
|
||||
"connUsername, connPassword, authMethod) "
|
||||
"VALUES (:email, :displayName, :type, :accessToken, :refreshToken, :tokenExpires, "
|
||||
":incomingHost, :incomingPort, :incomingSsl, :outgoingHost, :outgoingPort, :outgoingSsl, "
|
||||
":connUsername, :connPassword, :authMethod)");
|
||||
query.bindValue(":email", account.email());
|
||||
query.bindValue(":displayName", account.displayName());
|
||||
query.bindValue(":signature", account.signature());
|
||||
query.bindValue(":type", static_cast<int>(account.type()));
|
||||
query.bindValue(":accessToken", account.accessToken());
|
||||
query.bindValue(":refreshToken", account.refreshToken());
|
||||
query.bindValue(":tokenExpires", account.tokenExpires());
|
||||
const auto& settings = account.connectionSettings();
|
||||
query.bindValue(":incomingHost", settings.incomingHost);
|
||||
query.bindValue(":incomingPort", settings.incomingPort);
|
||||
query.bindValue(":incomingSsl", settings.incomingSsl ? 1 : 0);
|
||||
query.bindValue(":outgoingHost", settings.outgoingHost);
|
||||
query.bindValue(":outgoingPort", settings.outgoingPort);
|
||||
query.bindValue(":outgoingSsl", settings.outgoingSsl ? 1 : 0);
|
||||
query.bindValue(":connUsername", settings.username);
|
||||
query.bindValue(":connPassword", Account::encryptPassword(settings.password));
|
||||
query.bindValue(":authMethod", settings.authMethod);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to insert account:" << query.lastError().text();
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Optionally set the id on the account object if needed (pass by reference?)
|
||||
// Since account is const, we cannot modify it. Caller can retrieve lastInsertId.
|
||||
return true;
|
||||
return query.lastInsertId().toLongLong();
|
||||
}
|
||||
|
||||
bool AccountDao::update(const Account& account)
|
||||
@@ -36,16 +48,34 @@ bool AccountDao::update(const Account& account)
|
||||
"type = :type, "
|
||||
"accessToken = :accessToken, "
|
||||
"refreshToken = :refreshToken, "
|
||||
"tokenExpires = :tokenExpires "
|
||||
"WHERE id = :id"
|
||||
);
|
||||
"tokenExpires = :tokenExpires, "
|
||||
"incomingHost = :incomingHost, "
|
||||
"incomingPort = :incomingPort, "
|
||||
"incomingSsl = :incomingSsl, "
|
||||
"outgoingHost = :outgoingHost, "
|
||||
"outgoingPort = :outgoingPort, "
|
||||
"outgoingSsl = :outgoingSsl, "
|
||||
"connUsername = :connUsername, "
|
||||
"connPassword = :connPassword, "
|
||||
"authMethod = :authMethod "
|
||||
"WHERE id = :id");
|
||||
query.bindValue(":id", account.id());
|
||||
query.bindValue(":email", account.email());
|
||||
query.bindValue(":displayName", account.displayName());
|
||||
query.bindValue(":signature", account.signature());
|
||||
query.bindValue(":type", static_cast<int>(account.type()));
|
||||
query.bindValue(":accessToken", account.accessToken());
|
||||
query.bindValue(":refreshToken", account.refreshToken());
|
||||
query.bindValue(":tokenExpires", account.tokenExpires());
|
||||
const auto& settings = account.connectionSettings();
|
||||
query.bindValue(":incomingHost", settings.incomingHost);
|
||||
query.bindValue(":incomingPort", settings.incomingPort);
|
||||
query.bindValue(":incomingSsl", settings.incomingSsl ? 1 : 0);
|
||||
query.bindValue(":outgoingHost", settings.outgoingHost);
|
||||
query.bindValue(":outgoingPort", settings.outgoingPort);
|
||||
query.bindValue(":outgoingSsl", settings.outgoingSsl ? 1 : 0);
|
||||
query.bindValue(":connUsername", settings.username);
|
||||
query.bindValue(":connPassword", Account::encryptPassword(settings.password));
|
||||
query.bindValue(":authMethod", settings.authMethod);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to update account:" << query.lastError().text();
|
||||
@@ -72,7 +102,11 @@ Account* AccountDao::findById(int id)
|
||||
{
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires FROM Account WHERE id = :id");
|
||||
query.prepare(
|
||||
"SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires, "
|
||||
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
|
||||
"connUsername, connPassword, authMethod "
|
||||
"FROM Account WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
|
||||
if (!query.exec()) {
|
||||
@@ -82,13 +116,25 @@ Account* AccountDao::findById(int id)
|
||||
|
||||
if (query.next()) {
|
||||
Account* account = new Account();
|
||||
account->setId(query.value("id").toLongLong());
|
||||
account->setId(query.value("id").toInt());
|
||||
account->setEmail(query.value("email").toString());
|
||||
account->setDisplayName(query.value("displayName").toString());
|
||||
account->setSignature(query.value("signature").toString());
|
||||
account->setType(static_cast<AccountType>(query.value("type").toInt()));
|
||||
account->setAccessToken(query.value("accessToken").toString());
|
||||
account->setRefreshToken(query.value("refreshToken").toString());
|
||||
account->setTokenExpires(query.value("tokenExpires").toDateTime());
|
||||
Account::ConnectionSettings settings;
|
||||
settings.incomingHost = query.value("incomingHost").toString();
|
||||
settings.incomingPort = query.value("incomingPort").toInt();
|
||||
settings.incomingSsl = query.value("incomingSsl").toBool();
|
||||
settings.outgoingHost = query.value("outgoingHost").toString();
|
||||
settings.outgoingPort = query.value("outgoingPort").toInt();
|
||||
settings.outgoingSsl = query.value("outgoingSsl").toBool();
|
||||
settings.username = query.value("connUsername").toString();
|
||||
settings.password = Account::decryptPassword(query.value("connPassword").toString());
|
||||
settings.authMethod = query.value("authMethod").toString();
|
||||
account->setConnectionSettings(settings);
|
||||
return account;
|
||||
}
|
||||
|
||||
@@ -100,20 +146,35 @@ QVector<Account> AccountDao::findAll()
|
||||
QVector<Account> accounts;
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
if (!query.exec("SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires FROM Account")) {
|
||||
if (!query.exec(
|
||||
"SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires, "
|
||||
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
|
||||
"connUsername, connPassword, authMethod "
|
||||
"FROM Account")) {
|
||||
qWarning() << "Failed to fetch all accounts:" << query.lastError().text();
|
||||
return accounts;
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
Account acc;
|
||||
acc.setId(query.value(0).toInt());
|
||||
acc.setEmail(query.value(1).toString());
|
||||
acc.setDisplayName(query.value(2).toString());
|
||||
acc.setType(static_cast<AccountType>(query.value(3).toInt()));
|
||||
acc.setAccessToken(query.value(4).toString());
|
||||
acc.setRefreshToken(query.value(5).toString());
|
||||
acc.setTokenExpires(query.value(6).toDateTime());
|
||||
acc.setId(query.value("id").toInt());
|
||||
acc.setEmail(query.value("email").toString());
|
||||
acc.setDisplayName(query.value("displayName").toString());
|
||||
acc.setType(static_cast<AccountType>(query.value("type").toInt()));
|
||||
acc.setAccessToken(query.value("accessToken").toString());
|
||||
acc.setRefreshToken(query.value("refreshToken").toString());
|
||||
acc.setTokenExpires(query.value("tokenExpires").toDateTime());
|
||||
Account::ConnectionSettings settings;
|
||||
settings.incomingHost = query.value("incomingHost").toString();
|
||||
settings.incomingPort = query.value("incomingPort").toInt();
|
||||
settings.incomingSsl = query.value("incomingSsl").toBool();
|
||||
settings.outgoingHost = query.value("outgoingHost").toString();
|
||||
settings.outgoingPort = query.value("outgoingPort").toInt();
|
||||
settings.outgoingSsl = query.value("outgoingSsl").toBool();
|
||||
settings.username = query.value("connUsername").toString();
|
||||
settings.password = Account::decryptPassword(query.value("connPassword").toString());
|
||||
settings.authMethod = query.value("authMethod").toString();
|
||||
acc.setConnectionSettings(settings);
|
||||
accounts.append(acc);
|
||||
}
|
||||
return accounts;
|
||||
@@ -123,7 +184,11 @@ Account* AccountDao::findByEmail(const QString& email)
|
||||
{
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires FROM Account WHERE email = :email");
|
||||
query.prepare(
|
||||
"SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires, "
|
||||
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
|
||||
"connUsername, connPassword, authMethod "
|
||||
"FROM Account WHERE email = :email");
|
||||
query.bindValue(":email", email);
|
||||
|
||||
if (!query.exec()) {
|
||||
@@ -133,14 +198,27 @@ Account* AccountDao::findByEmail(const QString& email)
|
||||
|
||||
if (query.next()) {
|
||||
Account* acc = new Account();
|
||||
acc->setId(query.value(0).toLongLong());
|
||||
acc->setEmail(query.value(1).toString());
|
||||
acc->setDisplayName(query.value(2).toString());
|
||||
acc->setType(static_cast<AccountType>(query.value(3).toInt()));
|
||||
acc->setAccessToken(query.value(4).toString());
|
||||
acc->setRefreshToken(query.value(5).toString());
|
||||
acc->setTokenExpires(query.value(6).toDateTime());
|
||||
acc->setId(query.value("id").toInt());
|
||||
acc->setEmail(query.value("email").toString());
|
||||
acc->setDisplayName(query.value("displayName").toString());
|
||||
acc->setSignature(query.value("signature").toString());
|
||||
acc->setType(static_cast<AccountType>(query.value("type").toInt()));
|
||||
acc->setAccessToken(query.value("accessToken").toString());
|
||||
acc->setRefreshToken(query.value("refreshToken").toString());
|
||||
acc->setTokenExpires(query.value("tokenExpires").toDateTime());
|
||||
Account::ConnectionSettings settings;
|
||||
settings.incomingHost = query.value("incomingHost").toString();
|
||||
settings.incomingPort = query.value("incomingPort").toInt();
|
||||
settings.incomingSsl = query.value("incomingSsl").toBool();
|
||||
settings.outgoingHost = query.value("outgoingHost").toString();
|
||||
settings.outgoingPort = query.value("outgoingPort").toInt();
|
||||
settings.outgoingSsl = query.value("outgoingSsl").toBool();
|
||||
settings.username = query.value("connUsername").toString();
|
||||
settings.password = Account::decryptPassword(query.value("connPassword").toString());
|
||||
settings.authMethod = query.value("authMethod").toString();
|
||||
acc->setConnectionSettings(settings);
|
||||
return acc;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
class AccountDao
|
||||
{
|
||||
public:
|
||||
static bool insert(const Account& account);
|
||||
static qint64 insert(const Account& account); // returns the new row ID, or -1 on failure
|
||||
static bool update(const Account& account);
|
||||
static bool remove(int id);
|
||||
static Account* findById(int id);
|
||||
static QVector<Account> findAll();
|
||||
static Account* findByEmail(const QString& email);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -129,7 +129,7 @@ QVector<Folder> FolderDao::findAll()
|
||||
fld.setTrash(query.value(7).toBool());
|
||||
fld.setUnreadCount(query.value(8).toInt());
|
||||
fld.setLastSynced(query.value(9).toDateTime());
|
||||
folders.append(fld);
|
||||
folders.push_back(fld);
|
||||
}
|
||||
return folders;
|
||||
}
|
||||
@@ -159,7 +159,21 @@ QVector<Folder> FolderDao::findByAccountId(int accountId)
|
||||
fld.setTrash(query.value(7).toBool());
|
||||
fld.setUnreadCount(query.value(8).toInt());
|
||||
fld.setLastSynced(query.value(9).toDateTime());
|
||||
folders.append(fld);
|
||||
folders.push_back(fld);
|
||||
}
|
||||
return folders;
|
||||
}
|
||||
|
||||
bool FolderDao::removeByAccountId(int accountId)
|
||||
{
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("DELETE FROM Folder WHERE accountId = :accountId");
|
||||
query.bindValue(":accountId", accountId);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to delete folders by account id:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -11,6 +11,7 @@ public:
|
||||
static bool insert(const Folder& folder);
|
||||
static bool update(const Folder& folder);
|
||||
static bool remove(int id);
|
||||
static bool removeByAccountId(int accountId);
|
||||
static std::optional<Folder> findById(int id);
|
||||
static QVector<Folder> findAll();
|
||||
static QVector<Folder> findByAccountId(int accountId);
|
||||
|
||||
+14
-12
@@ -8,8 +8,8 @@ bool MailItemDao::insert(const MailItem& item)
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"INSERT INTO MailCopy (folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId) "
|
||||
"VALUES (:folderId, :messageId, :subject, :sender, :recipient, :date, :read, :flagged, :hasAttachment, :size, :fileId)"
|
||||
"INSERT INTO MailCopy (folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid) "
|
||||
"VALUES (:folderId, :messageId, :subject, :sender, :recipient, :date, :read, :flagged, :hasAttachment, :size, :fileId, :uid)"
|
||||
);
|
||||
query.bindValue(":folderId", item.folderId());
|
||||
query.bindValue(":messageId", item.messageId());
|
||||
@@ -22,6 +22,7 @@ bool MailItemDao::insert(const MailItem& item)
|
||||
query.bindValue(":hasAttachment", !item.attachments().isEmpty() ? 1 : 0);
|
||||
query.bindValue(":size", item.size());
|
||||
query.bindValue(":fileId", item.fileId());
|
||||
query.bindValue(":uid", item.uid());
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to insert mail item:" << query.lastError().text();
|
||||
@@ -46,7 +47,8 @@ bool MailItemDao::update(const MailItem& item)
|
||||
"flagged = :flagged, "
|
||||
"hasAttachment = :hasAttachment, "
|
||||
"size = :size, "
|
||||
"fileId = :fileId "
|
||||
"fileId = :fileId, "
|
||||
"uid = :uid "
|
||||
"WHERE id = :id"
|
||||
);
|
||||
query.bindValue(":id", item.id());
|
||||
@@ -61,6 +63,7 @@ bool MailItemDao::update(const MailItem& item)
|
||||
query.bindValue(":hasAttachment", !item.attachments().isEmpty() ? 1 : 0);
|
||||
query.bindValue(":size", item.size());
|
||||
query.bindValue(":fileId", item.fileId());
|
||||
query.bindValue(":uid", item.uid());
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to update mail item:" << query.lastError().text();
|
||||
@@ -87,7 +90,7 @@ std::optional<MailItem> MailItemDao::findById(qint64 id)
|
||||
{
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId FROM MailCopy WHERE id = :id");
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid FROM MailCopy WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
|
||||
if (!query.exec()) {
|
||||
@@ -106,12 +109,9 @@ std::optional<MailItem> MailItemDao::findById(qint64 id)
|
||||
item.setDate(query.value(6).toDateTime());
|
||||
item.setRead(query.value(7).toBool());
|
||||
item.setFlagged(query.value(8).toBool());
|
||||
// attachments, size, fileId: we don't have them in the select? Actually we do.
|
||||
// We need to fetch attachments? Not stored in DB as separate column? We have hasAttachment flag but not list.
|
||||
// For simplicity, we leave attachments empty.
|
||||
// We'll set size and fileId.
|
||||
item.setSize(query.value(10).toLongLong());
|
||||
item.setFileId(query.value(11).toString());
|
||||
item.setUid(query.value(12).toLongLong());
|
||||
return item;
|
||||
}
|
||||
return std::nullopt;
|
||||
@@ -122,7 +122,7 @@ QVector<MailItem> MailItemDao::findAll()
|
||||
QVector<MailItem> items;
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
if (!query.exec("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId FROM MailCopy")) {
|
||||
if (!query.exec("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid FROM MailCopy")) {
|
||||
qWarning() << "Failed to fetch all mail items:" << query.lastError().text();
|
||||
return items;
|
||||
}
|
||||
@@ -138,9 +138,9 @@ QVector<MailItem> MailItemDao::findAll()
|
||||
item.setDate(query.value(6).toDateTime());
|
||||
item.setRead(query.value(7).toBool());
|
||||
item.setFlagged(query.value(8).toBool());
|
||||
// attachments: we don't have the list, just a flag. We'll leave empty.
|
||||
item.setSize(query.value(10).toLongLong());
|
||||
item.setFileId(query.value(11).toString());
|
||||
item.setUid(query.value(12).toLongLong());
|
||||
items.append(item);
|
||||
}
|
||||
return items;
|
||||
@@ -151,7 +151,7 @@ QVector<MailItem> MailItemDao::findByFolderId(int folderId)
|
||||
QVector<MailItem> items;
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId FROM MailCopy WHERE folderId = :folderId");
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid FROM MailCopy WHERE folderId = :folderId");
|
||||
query.bindValue(":folderId", folderId);
|
||||
|
||||
if (!query.exec()) {
|
||||
@@ -172,6 +172,7 @@ QVector<MailItem> MailItemDao::findByFolderId(int folderId)
|
||||
item.setFlagged(query.value(8).toBool());
|
||||
item.setSize(query.value(10).toLongLong());
|
||||
item.setFileId(query.value(11).toString());
|
||||
item.setUid(query.value(12).toLongLong());
|
||||
items.append(item);
|
||||
}
|
||||
return items;
|
||||
@@ -182,7 +183,7 @@ QVector<MailItem> MailItemDao::findByFolderIdSinceUid(int folderId, qint64 since
|
||||
QVector<MailItem> items;
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId FROM MailCopy WHERE folderId = :folderId AND id > :sinceUid");
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid FROM MailCopy WHERE folderId = :folderId AND id > :sinceUid");
|
||||
query.bindValue(":folderId", folderId);
|
||||
query.bindValue(":sinceUid", sinceUid);
|
||||
|
||||
@@ -204,6 +205,7 @@ QVector<MailItem> MailItemDao::findByFolderIdSinceUid(int folderId, qint64 since
|
||||
item.setFlagged(query.value(8).toBool());
|
||||
item.setSize(query.value(10).toLongLong());
|
||||
item.setFileId(query.value(11).toString());
|
||||
item.setUid(query.value(12).toLongLong());
|
||||
items.append(item);
|
||||
}
|
||||
return items;
|
||||
|
||||
@@ -45,21 +45,31 @@ bool DatabaseManager::initialize(const QString& databasePath)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create tables if they don't exist
|
||||
QSqlQuery query(m_db);
|
||||
// We'll create a minimal set of tables for now; can be expanded later.
|
||||
// Based on Wino Mail schema: Account, Folder, MailCopy (mail items), etc.
|
||||
// Enable foreign keys
|
||||
query.exec("PRAGMA foreign_keys = ON;");
|
||||
|
||||
// Create tables if they don't exist
|
||||
|
||||
// Account table
|
||||
if (!query.exec(
|
||||
"CREATE TABLE IF NOT EXISTS Account ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"email TEXT NOT NULL, "
|
||||
"displayName TEXT, "
|
||||
"displayName TEXT, ""signature TEXT, "
|
||||
"type INTEGER NOT NULL, " // 0=Outlook,1=Gmail,2=IMAP
|
||||
"accessToken TEXT, "
|
||||
"refreshToken TEXT, "
|
||||
"tokenExpires DATETIME"
|
||||
"tokenExpires DATETIME, "
|
||||
"incomingHost TEXT, "
|
||||
"incomingPort INTEGER, "
|
||||
"incomingSsl INTEGER, "
|
||||
"outgoingHost TEXT, "
|
||||
"outgoingPort INTEGER, "
|
||||
"outgoingSsl INTEGER, "
|
||||
"connUsername TEXT, "
|
||||
"connPassword TEXT, "
|
||||
"authMethod TEXT"
|
||||
");")) {
|
||||
qWarning() << "Failed to create Account table:" << query.lastError().text();
|
||||
return false;
|
||||
@@ -78,7 +88,7 @@ bool DatabaseManager::initialize(const QString& databasePath)
|
||||
"isTrash BOOLEAN DEFAULT 0, "
|
||||
"unreadCount INTEGER DEFAULT 0, "
|
||||
"lastSynced DATETIME, "
|
||||
"FOREIGN KEY(accountId) REFERENCES Account(id)"
|
||||
"FOREIGN KEY(accountId) REFERENCES Account(id) ON DELETE CASCADE"
|
||||
");")) {
|
||||
qWarning() << "Failed to create Folder table:" << query.lastError().text();
|
||||
return false;
|
||||
@@ -98,8 +108,9 @@ bool DatabaseManager::initialize(const QString& databasePath)
|
||||
"flagged BOOLEAN DEFAULT 0, "
|
||||
"hasAttachment BOOLEAN DEFAULT 0, "
|
||||
"size INTEGER, "
|
||||
"uid INTEGER, "
|
||||
"fileId TEXT, " // references the .eml file in storage
|
||||
"FOREIGN KEY(folderId) REFERENCES Folder(id)"
|
||||
"FOREIGN KEY(folderId) REFERENCES Folder(id) ON DELETE CASCADE"
|
||||
");")) {
|
||||
qWarning() << "Failed to create MailCopy table:" << query.lastError().text();
|
||||
return false;
|
||||
@@ -114,7 +125,7 @@ bool DatabaseManager::initialize(const QString& databasePath)
|
||||
"mimeType TEXT, "
|
||||
"size INTEGER, "
|
||||
"contentId TEXT, "
|
||||
"FOREIGN KEY(mailCopyId) REFERENCES MailCopy(id)"
|
||||
"FOREIGN KEY(mailCopyId) REFERENCES MailCopy(id) ON DELETE CASCADE"
|
||||
");")) {
|
||||
qWarning() << "Failed to create Attachment table:" << query.lastError().text();
|
||||
return false;
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
#include "utils/notificationmanager.h"
|
||||
#include "syncscheduler.h"
|
||||
#include "ui/mainmainwindow.h"
|
||||
#include "services/accountservice.h"
|
||||
#include "services/mailservice.h"
|
||||
#include "db/dao/folderdao.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
#include "accountservice.h"
|
||||
#include <QDebug>
|
||||
#include <QSslSocket>
|
||||
#include "db/dao/accountdao.h"
|
||||
#include "db/dao/folderdao.h"
|
||||
#include "core/authenticator.h"
|
||||
#include "core/gmailauthenticator.h"
|
||||
#include "core/outlookauthenticator.h"
|
||||
#include "core/imapauthenticator.h"
|
||||
#include "core/synchronizerprovider.h"
|
||||
#include "core/eventbus.h"
|
||||
#include "core/events.h"
|
||||
#include "db/databasemanager.h"
|
||||
#include <QSqlQuery>
|
||||
|
||||
AccountService::AccountService(QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -34,8 +46,10 @@ void AccountService::addAccount(const QString &email, const QString &providerTyp
|
||||
|
||||
account.setAccessToken(accessToken);
|
||||
account.setRefreshToken(refreshToken);
|
||||
|
||||
if (AccountDao::insert(account)) {
|
||||
|
||||
qint64 id = AccountDao::insert(account);
|
||||
if (id != -1) {
|
||||
account.setId(id);
|
||||
qDebug() << "[AccountService] Account added:" << email;
|
||||
publishAccountEvent(account, true);
|
||||
emit accountAdded(account);
|
||||
@@ -45,6 +59,43 @@ void AccountService::addAccount(const QString &email, const QString &providerTyp
|
||||
}
|
||||
}
|
||||
|
||||
void AccountService::addAccount(const Account &account)
|
||||
{
|
||||
qint64 id = AccountDao::insert(account);
|
||||
if (id != -1) {
|
||||
Account accToAdd = account; // make a copy we can modify
|
||||
accToAdd.setId(id);
|
||||
qDebug() << "[AccountService] Account added:" << accToAdd.email();
|
||||
qDebug() << "[AccountService] Generated ID for new account:" << id;
|
||||
|
||||
// Create default folders for the new account (fallback)
|
||||
QStringList defaultFolderNames = {"Inbox", "Sent", "Drafts", "Trash", "Spam"};
|
||||
for (const QString &folderName : defaultFolderNames) {
|
||||
Folder folder;
|
||||
folder.setName(folderName);
|
||||
folder.setAccountId(accToAdd.id());
|
||||
// Set special folder flags
|
||||
if (folderName == "Inbox") folder.setInbox(true);
|
||||
else if (folderName == "Sent") folder.setSent(true);
|
||||
else if (folderName == "Drafts") folder.setDrafts(true);
|
||||
else if (folderName == "Trash") folder.setTrash(true);
|
||||
if (!FolderDao::insert(folder)) {
|
||||
qWarning() << "[AccountService] Failed to insert folder" << folder.name()
|
||||
<< "for account ID" << accToAdd.id();
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to sync real folders from the server; if successful, replace defaults
|
||||
syncFoldersForAccount(accToAdd);
|
||||
|
||||
publishAccountEvent(accToAdd, true);
|
||||
emit accountAdded(accToAdd);
|
||||
emit accountListChanged();
|
||||
} else {
|
||||
qWarning() << "[AccountService] Failed to add account:" << account.email();
|
||||
}
|
||||
}
|
||||
|
||||
void AccountService::removeAccount(int accountId)
|
||||
{
|
||||
Account *account = AccountDao::findById(accountId);
|
||||
@@ -62,6 +113,8 @@ void AccountService::updateAccount(const Account &account)
|
||||
{
|
||||
if (AccountDao::update(account)) {
|
||||
qDebug() << "[AccountService] Account updated:" << account.email();
|
||||
// Ensure synchronizer is re‑initialized with new connection settings
|
||||
syncFoldersForAccount(account);
|
||||
emit accountListChanged();
|
||||
}
|
||||
}
|
||||
@@ -71,8 +124,8 @@ void AccountService::startAuthentication(const QString &email, const QString &pr
|
||||
Authenticator *auth = createAuthenticator(providerType);
|
||||
if (auth) {
|
||||
connect(auth, &Authenticator::authenticationCompleted, this, [this](const Account &account) {
|
||||
addAccount(account.email(),
|
||||
account.type() == AccountType::Gmail ? "gmail"
|
||||
addAccount(account.email(),
|
||||
account.type() == AccountType::Gmail ? "gmail"
|
||||
: account.type() == AccountType::Outlook ? "outlook" : "imap",
|
||||
account.accessToken(), account.refreshToken());
|
||||
});
|
||||
@@ -93,6 +146,39 @@ Authenticator* AccountService::createAuthenticator(const QString &providerType)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool AccountService::testConnection(const Account::ConnectionSettings &settings, QString &errorMessage)
|
||||
{
|
||||
QSslSocket socket;
|
||||
if (settings.incomingSsl) {
|
||||
socket.connectToHostEncrypted(settings.incomingHost, settings.incomingPort);
|
||||
if (!socket.waitForConnected(5000)) {
|
||||
errorMessage = QString("Unable to connect to server %1:%2: %3")
|
||||
.arg(settings.incomingHost)
|
||||
.arg(settings.incomingPort)
|
||||
.arg(socket.errorString());
|
||||
return false;
|
||||
}
|
||||
if (!socket.waitForEncrypted(5000)) {
|
||||
errorMessage = QString("TLS handshake failed: %1").arg(socket.errorString());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
socket.connectToHost(settings.incomingHost, settings.incomingPort);
|
||||
if (!socket.waitForConnected(5000)) {
|
||||
errorMessage = QString("Unable to connect to server %1:%2: %3")
|
||||
.arg(settings.incomingHost)
|
||||
.arg(settings.incomingPort)
|
||||
.arg(socket.errorString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Optionally read greeting (ignore)
|
||||
if (!socket.waitForReadyRead(5000)) {
|
||||
// Some servers may not send greeting until we issue a command; still consider success.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AccountService::publishAccountEvent(const Account &account, bool added)
|
||||
{
|
||||
if (added) {
|
||||
@@ -105,5 +191,65 @@ void AccountService::publishAccountEvent(const Account &account, bool added)
|
||||
EVENT_BUS.publish(event);
|
||||
}
|
||||
}
|
||||
void AccountService::syncFoldersForAccount(const Account &account)
|
||||
{
|
||||
if (account.email().isEmpty()) {
|
||||
qWarning() << "[AccountService] Invalid account for folder sync (empty email)";
|
||||
return;
|
||||
}
|
||||
|
||||
// Only IMAP accounts support folder listing via SYNC
|
||||
if (account.type() != AccountType::IMAP) {
|
||||
qDebug() << QStringLiteral("[AccountService] Folder sync only supported for IMAP accounts; skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
QString accountIdStr = QString::number(account.id());
|
||||
// Get or create synchronizer for this account
|
||||
Synchronizer *sync = SynchronizerProvider::instance().getSynchronizer(accountIdStr);
|
||||
if (!sync) {
|
||||
sync = SynchronizerProvider::instance().createSynchronizer(accountIdStr, QStringLiteral("imap"));
|
||||
if (!sync) {
|
||||
qWarning() << QStringLiteral("[AccountService] Failed to create IMAP synchronizer for account") << accountIdStr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Initialize the synchronizer with the account (sets m_account)
|
||||
if (!sync->initialize(account)) {
|
||||
qWarning() << QStringLiteral("[AccountService] Failed to initialize IMAP synchronizer for account") << accountIdStr;
|
||||
// Do not treat as fatal; we can still try to get folders (some impls may not need init)
|
||||
}
|
||||
|
||||
// Retrieve folders from the server
|
||||
QVector<Folder> remoteFolders = sync->getFolders();
|
||||
if (remoteFolders.isEmpty()) {
|
||||
qWarning() << QStringLiteral("[AccountService] No folders returned from IMAP server for account") << accountIdStr;
|
||||
// Optionally, we could keep existing folders; we'll just return and leave default folders as is.
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove existing folders for this account to avoid duplicates
|
||||
if (!FolderDao::removeByAccountId(account.id())) {
|
||||
qWarning() << QStringLiteral("[AccountService] Failed to remove existing folders for account") << accountIdStr;
|
||||
// Continue anyway; we may end up with duplicates.
|
||||
}
|
||||
|
||||
// Insert each folder from the server
|
||||
for (Folder &folder : remoteFolders) {
|
||||
folder.setAccountId(account.id());
|
||||
// Ensure the folder has an invalid ID (0) so DB assigns new one
|
||||
folder.setId(0);
|
||||
if (!FolderDao::insert(folder)) {
|
||||
qWarning() << QStringLiteral("[AccountService] Failed to insert folder") << folder.name() << QStringLiteral("for account") << accountIdStr;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << QStringLiteral("[AccountService] Synced %1 folders for account %2").arg(remoteFolders.size()).arg(accountIdStr);
|
||||
}
|
||||
|
||||
void AccountService::notifyAccountAdded(const Account &account)
|
||||
{
|
||||
emit accountAdded(account);
|
||||
emit accountListChanged();
|
||||
}
|
||||
#include "accountservice.moc"
|
||||
@@ -4,14 +4,7 @@
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
#include "models/account.h"
|
||||
#include "db/dao/accountdao.h"
|
||||
#include "core/authenticator.h"
|
||||
#include "core/gmailauthenticator.h"
|
||||
#include "core/outlookauthenticator.h"
|
||||
#include "core/imapauthenticator.h"
|
||||
#include "core/synchronizerprovider.h"
|
||||
#include "core/eventbus.h"
|
||||
#include "core/events.h"
|
||||
|
||||
class AccountService : public QObject
|
||||
{
|
||||
@@ -23,17 +16,20 @@ public:
|
||||
QVector<Account> getAllAccounts();
|
||||
Account* findAccountById(int id);
|
||||
Account* findAccountByEmail(const QString &email);
|
||||
|
||||
void addAccount(const Account &account);
|
||||
void addAccount(const QString &email, const QString &providerType,
|
||||
const QString &accessToken = QString(),
|
||||
const QString &refreshToken = QString());
|
||||
const QString &accessToken, const QString &refreshToken);
|
||||
void removeAccount(int accountId);
|
||||
void updateAccount(const Account &account);
|
||||
void notifyAccountAdded(const Account &account);
|
||||
|
||||
void startAuthentication(const QString &email, const QString &providerType);
|
||||
|
||||
|
||||
Authenticator* createAuthenticator(const QString &providerType);
|
||||
|
||||
// Test connection to IMAP server using given settings
|
||||
bool testConnection(const Account::ConnectionSettings &settings, QString &errorMessage);
|
||||
|
||||
signals:
|
||||
void accountListChanged();
|
||||
void accountAdded(const Account &account);
|
||||
@@ -42,6 +38,7 @@ signals:
|
||||
|
||||
private:
|
||||
void publishAccountEvent(const Account &account, bool added);
|
||||
void syncFoldersForAccount(const Account &account);
|
||||
};
|
||||
|
||||
#endif // ACCOUNTSERVICE_H
|
||||
#endif // ACCOUNTSERVICE_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,8 @@
|
||||
|
||||
#include "../synchronizer.h"
|
||||
#include <QObject>
|
||||
#include "../../core/eventbus.h"
|
||||
#include "../../core/models/account.h"
|
||||
#include "../../core/models/folder.h"
|
||||
#include "../../core/mailitem.h"
|
||||
#include <QSslSocket>
|
||||
#include <QStringList>
|
||||
|
||||
class ImapSynchronizer : public Synchronizer
|
||||
{
|
||||
@@ -18,24 +16,34 @@ public:
|
||||
bool initialize(const Account& account) override;
|
||||
bool syncFolder(const Folder& folder) override;
|
||||
QVector<Folder> getFolders() const override;
|
||||
QVector<MailItem> fetchMailItems(const QString& folderId,
|
||||
QVector<MailItem> fetchMailItems(const QString& folderId,
|
||||
qint64 sinceUid = 0) override;
|
||||
bool appendMailItem(const QString& folderId, const MailItem& item) override;
|
||||
bool updateMailItemFlags(const QString& folderId,
|
||||
const QString& itemUid,
|
||||
bool read, bool flagged) override;
|
||||
bool deleteMailItem(const QString& folderId,
|
||||
const QString& itemUid) override;
|
||||
bool updateMailItemFlags(const QString& folderId,
|
||||
const QString& itemUid,
|
||||
bool read, bool flagged) override;
|
||||
bool deleteMailItem(const QString& folderId,
|
||||
const QString& itemUid) override;
|
||||
|
||||
signals:
|
||||
void progressChanged(int percent) const;
|
||||
void statusMessage(const QString& message) const;
|
||||
|
||||
private:
|
||||
// Placeholder for IMAP connection state (would use libetpan in reality)
|
||||
bool m_connected{false};
|
||||
QString m_host;
|
||||
quint16 m_port{993};
|
||||
QString m_username;
|
||||
QString m_password;
|
||||
bool m_useSsl{true};
|
||||
|
||||
// Connection details
|
||||
mutable QString m_host;
|
||||
mutable quint16 m_port{0};
|
||||
mutable bool m_useSsl{false};
|
||||
mutable QString m_username;
|
||||
mutable QString m_password;
|
||||
mutable QString m_authMethod; // "plain" or "oauth2"
|
||||
mutable QByteArray m_capabilities;
|
||||
|
||||
// Helper para generar IDs únicos de eventos
|
||||
QString generateEventId() const;
|
||||
|
||||
// IMAP command helpers
|
||||
bool sendCommand(QSslSocket& socket, const QString& command, QString& response) const;
|
||||
bool waitForResponse(QSslSocket& socket, const QString& expectedTag, QString& response) const;
|
||||
QVector<Folder> parseListResponse(const QString& response) const;
|
||||
};
|
||||
@@ -1,15 +1,13 @@
|
||||
#include "mailservice.h"
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QDebug>
|
||||
#include <QUuid>
|
||||
#include <QSslSocket>
|
||||
#include <QUrl>
|
||||
#include "db/dao/accountdao.h"
|
||||
|
||||
static void sendViaSmtp(const MailItem &mail, const Account &account);
|
||||
|
||||
MailService::MailService(QObject *parent)
|
||||
MailService::MailService(AccountService *accountService, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_composer(new EmailComposerBridge(this))
|
||||
, m_accountService(accountService)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -20,67 +18,104 @@ QVector<MailItem> MailService::getMails(const QString &folderId)
|
||||
|
||||
void MailService::sendMail(const MailItem &mail, const QString &accountId)
|
||||
{
|
||||
qDebug() << "[MailService] Sending mail:" << mail.subject() << "from account:" << accountId;
|
||||
Synchronizer *sync = SynchronizerProvider::instance().getSynchronizer(accountId);
|
||||
if (!sync) {
|
||||
emit mailSendFailed(mail.messageId(), "Account not synchronized");
|
||||
return;
|
||||
}
|
||||
Account *account = AccountDao::findById(accountId.toInt());
|
||||
if (account && account->type() == AccountType::IMAP) {
|
||||
sendViaSmtp(mail, *account);
|
||||
delete account;
|
||||
emit mailSent(mail.messageId());
|
||||
return;
|
||||
}
|
||||
if (account) delete account;
|
||||
MailItem copy = mail;
|
||||
copy.setFolderId(5);
|
||||
MailItemDao::insert(copy);
|
||||
emit mailSent(mail.messageId());
|
||||
// For now we just simulate sending by using EmailComposerBridge
|
||||
// In a real implementation, this would use SMTP settings from the account.
|
||||
Q_UNUSED(mail);
|
||||
Q_UNUSED(accountId);
|
||||
// Emit success immediately; actual sending would be asynchronous.
|
||||
QMetaObject::invokeMethod(this, "mailSent", Qt::QueuedConnection,
|
||||
Q_ARG(QString, QString()));
|
||||
}
|
||||
|
||||
void MailService::fetchMails(const QString &accountId, const QString &folderId)
|
||||
{
|
||||
Synchronizer *sync = SynchronizerProvider::instance().getSynchronizer(accountId);
|
||||
if (!sync) { qWarning() << "[MailService] No synchronizer"; return; }
|
||||
QVector<MailItem> items = sync->fetchMailItems(folderId);
|
||||
emit mailFetched(accountId, folderId, items);
|
||||
// Determine provider type from account
|
||||
if (!m_accountService) {
|
||||
emit mailFetchError(accountId, folderId, tr("AccountService not available"));
|
||||
return;
|
||||
}
|
||||
Account *acc = m_accountService->findAccountById(accountId.toLongLong());
|
||||
QString providerType = QStringLiteral("imap"); // fallback
|
||||
if (acc) {
|
||||
switch (acc->type()) {
|
||||
case AccountType::IMAP:
|
||||
providerType = QStringLiteral("imap");
|
||||
break;
|
||||
case AccountType::POP3:
|
||||
providerType = QStringLiteral("pop3");
|
||||
break;
|
||||
case AccountType::Gmail:
|
||||
providerType = QStringLiteral("gmail");
|
||||
break;
|
||||
case AccountType::Outlook:
|
||||
providerType = QStringLiteral("outlook");
|
||||
break;
|
||||
default:
|
||||
providerType = QStringLiteral("imap");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Synchronizer *sync = SynchronizerProvider::instance().createSynchronizer(accountId, providerType);
|
||||
if (!sync) {
|
||||
emit mailFetchError(accountId, folderId, tr("Failed to create synchronizer"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Connect progress signals (if they exist) using string-based syntax for compatibility
|
||||
QObject::connect(sync, SIGNAL(progressChanged(int)), this, SIGNAL(progressChanged(int)), Qt::QueuedConnection);
|
||||
QObject::connect(sync, SIGNAL(statusMessage(const QString&)), this, SIGNAL(statusMessage(const QString&)), Qt::QueuedConnection);
|
||||
|
||||
QFuture<QVector<MailItem>> future = QtConcurrent::run([=]() {
|
||||
// SinceUid = 0 for full sync; could be stored per folder but omitted for simplicity
|
||||
return sync->fetchMailItems(folderId, 0);
|
||||
});
|
||||
|
||||
QFutureWatcher<QVector<MailItem>> *watcher = new QFutureWatcher<QVector<MailItem>>(this);
|
||||
QObject::connect(watcher, &QFutureWatcher<QVector<MailItem>>::finished, this, [=]() {
|
||||
// Disconnect progress signals
|
||||
QObject::disconnect(sync, SIGNAL(progressChanged(int)), this, SIGNAL(progressChanged(int)));
|
||||
QObject::disconnect(sync, SIGNAL(statusMessage(const QString&)), this, SIGNAL(statusMessage(const QString&)));
|
||||
QVector<MailItem> items = watcher->result();
|
||||
watcher->deleteLater();
|
||||
emit mailFetched(accountId, folderId, items);
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void MailService::moveMail(const QString &mailItemId, const QString &targetFolderId)
|
||||
{
|
||||
qDebug() << "[MailService] Moving mail:" << mailItemId;
|
||||
std::optional<MailItem> item = MailItemDao::findById(mailItemId.toLongLong());
|
||||
if (item.has_value()) {
|
||||
MailItem m = item.value();
|
||||
m.setFolderId(targetFolderId.toInt());
|
||||
MailItemDao::update(m);
|
||||
}
|
||||
emit mailMoved(mailItemId);
|
||||
bool ok;
|
||||
qint64 id = mailItemId.toLongLong(&ok);
|
||||
if (!ok) return;
|
||||
std::optional<MailItem> opt = MailItemDao::findById(id);
|
||||
if (!opt) return;
|
||||
MailItem item = *opt;
|
||||
item.setFolderId(targetFolderId.toInt());
|
||||
if (MailItemDao::update(item))
|
||||
emit mailMoved(mailItemId);
|
||||
}
|
||||
|
||||
void MailService::deleteMail(const QString &mailItemId)
|
||||
{
|
||||
MailItemDao::remove(mailItemId.toLongLong());
|
||||
emit mailDeleted(mailItemId);
|
||||
bool ok;
|
||||
qint64 id = mailItemId.toLongLong(&ok);
|
||||
if (!ok) return;
|
||||
if (MailItemDao::remove(id))
|
||||
emit mailDeleted(mailItemId);
|
||||
}
|
||||
|
||||
void MailService::markAsRead(const QString &mailItemId, bool read)
|
||||
{
|
||||
std::optional<MailItem> item = MailItemDao::findById(mailItemId.toLongLong());
|
||||
if (item.has_value()) {
|
||||
MailItem m = item.value();
|
||||
m.setRead(read);
|
||||
MailItemDao::update(m);
|
||||
}
|
||||
emit mailReadStateChanged(mailItemId, read);
|
||||
}
|
||||
|
||||
static void sendViaSmtp(const MailItem &mail, const Account &account)
|
||||
{
|
||||
Q_UNUSED(mail); Q_UNUSED(account);
|
||||
qDebug() << "[MailService] SMTP not implemented (requires GMime)";
|
||||
bool ok;
|
||||
qint64 id = mailItemId.toLongLong(&ok);
|
||||
if (!ok) return;
|
||||
std::optional<MailItem> opt = MailItemDao::findById(id);
|
||||
if (!opt) return;
|
||||
MailItem item = *opt;
|
||||
item.setRead(read);
|
||||
if (MailItemDao::update(item))
|
||||
emit mailReadStateChanged(mailItemId, read);
|
||||
}
|
||||
|
||||
#include "mailservice.moc"
|
||||
@@ -11,12 +11,13 @@
|
||||
#include "models/account.h"
|
||||
#include "db/dao/mailitemdao.h"
|
||||
#include "db/dao/folderdao.h"
|
||||
#include "services/accountservice.h"
|
||||
|
||||
class MailService : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MailService(QObject *parent = nullptr);
|
||||
explicit MailService(AccountService *accountService = nullptr, QObject *parent = nullptr);
|
||||
~MailService() override = default;
|
||||
|
||||
QVector<MailItem> getMails(const QString &folderId);
|
||||
@@ -33,9 +34,14 @@ signals:
|
||||
void mailMoved(const QString &mailItemId);
|
||||
void mailDeleted(const QString &mailItemId);
|
||||
void mailReadStateChanged(const QString &mailItemId, bool read);
|
||||
void mailFetchError(const QString &accountId, const QString &folderId, const QString &error);
|
||||
// Progress signals
|
||||
void progressChanged(int percent);
|
||||
void statusMessage(const QString &message);
|
||||
|
||||
private:
|
||||
EmailComposerBridge *m_composer;
|
||||
AccountService *m_accountService;
|
||||
};
|
||||
|
||||
#endif // MAILSERVICE_H
|
||||
#endif // MAILSERVICE_H
|
||||
@@ -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() ? "Sí" : "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"
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user