Baseline for wino-mail-dtkqt UI migration

This commit is contained in:
2026-06-18 18:58:27 +02:00
parent 0e9f620fe0
commit 2a3a1a0470
10 changed files with 80 additions and 77 deletions
Binary file not shown.
+25 -15
View File
@@ -9,33 +9,43 @@ Authenticator::Authenticator(QObject *parent)
void Authenticator::exchangeAuthorizationCode(const QString &code, const QString &redirectUri) void Authenticator::exchangeAuthorizationCode(const QString &code, const QString &redirectUri)
{ {
Q_UNUSED(code); QUrl url(m_tokenEndpoint);
Q_UNUSED(redirectUri); QNetworkRequest request = createTokenRequest(url);
qDebug() << "[Authenticator] exchangeAuthorizationCode called (base implementation)";
QByteArray postData;
postData.append("grant_type=authorization_code");
postData.append("&code=" + code.toUtf8());
postData.append("&redirect_uri=" + redirectUri.toUtf8());
postData.append("&client_id=" + QString(m_clientId).toUtf8());
postData.append("&client_secret=" + QString(m_clientSecret).toUtf8());
QNetworkReply *reply = m_networkManager->post(request, postData);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
handleTokenResponse(reply->readAll(), m_email, providerName());
reply->deleteLater();
});
} }
void Authenticator::handleTokenResponse(const QByteArray &data, const QString &email, const QString &providerType) void Authenticator::handleTokenResponse(const QByteArray &data, const QString &email, const QString &providerType)
{ {
QJsonDocument doc = QJsonDocument::fromJson(data); QJsonDocument doc = QJsonDocument::fromJson(data);
if (doc.isNull() || !doc.isObject()) { if (!doc.isObject()) {
emit authenticationFailed("Invalid token response"); emit authenticationFailed("Invalid response from server");
return; return;
} }
QJsonObject obj = doc.object(); QJsonObject obj = doc.object();
QString accessToken = obj["access_token"].toString(); if (obj.contains("error")) {
QString refreshToken = obj["refresh_token"].toString(); emit authenticationFailed(obj["error"].toString() + ": " + obj["error_description"].toString());
int expiresIn = obj["expires_in"].toInt(); return;
}
Account account; Account account;
account.setEmail(email); account.setEmail(email);
account.setAccessToken(accessToken); account.setProvider(providerType);
account.setRefreshToken(refreshToken); account.setAccessToken(obj["access_token"].toString());
account.setTokenExpires(QDateTime::currentDateTimeUtc().addSecs(expiresIn)); account.setRefreshToken(obj["refresh_token"].toString());
account.setTokenExpires(QDateTime::currentDateTimeUtc().addSecs(obj["expires_in"].toInt()));
if (providerType == "gmail") account.setType(AccountType::Gmail);
else if (providerType == "outlook") account.setType(AccountType::Outlook);
else account.setType(AccountType::IMAP);
emit authenticationCompleted(account); emit authenticationCompleted(account);
} }
+5 -30
View File
@@ -24,7 +24,7 @@ GmailAuthenticator::GmailAuthenticator(QObject *parent)
connect(m_callbackServer, &OAuthCallbackServer::codeReceived, connect(m_callbackServer, &OAuthCallbackServer::codeReceived,
this, &GmailAuthenticator::onAuthCodeReceived); this, &GmailAuthenticator::onAuthCodeReceived);
connect(m_callbackServer, &OAuthCallbackServer::errorOccurred, connect(m_callbackServer, &OAuthCallbackServer::errorOccurred,
this, [this](const QString &error) { [this](const QString &error) {
emit authenticationFailed("OAuth callback error: " + error); emit authenticationFailed("OAuth callback error: " + error);
}); });
} }
@@ -66,19 +66,8 @@ void GmailAuthenticator::onAuthCodeReceived(const QString &code, const QString &
m_authCode = code; m_authCode = code;
// Exchange the authorization code for tokens // Exchange the authorization code for tokens using base class implementation
QUrlQuery params; exchangeAuthorizationCode(code, m_redirectUri);
params.addQueryItem("code", code);
params.addQueryItem("client_id", m_clientId);
params.addQueryItem("client_secret", m_clientSecret);
params.addQueryItem("redirect_uri", m_redirectUri);
params.addQueryItem("grant_type", "authorization_code");
QNetworkRequest request = createTokenRequest(QUrl(m_tokenEndpoint));
QNetworkReply *reply = m_networkManager->post(request, params.toString(QUrl::FullyEncoded).toUtf8());
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
onTokenReply(reply);
});
} }
void GmailAuthenticator::refreshToken(const Account &account) void GmailAuthenticator::refreshToken(const Account &account)
@@ -94,23 +83,9 @@ void GmailAuthenticator::refreshToken(const Account &account)
QNetworkRequest request = createTokenRequest(QUrl(m_tokenEndpoint)); QNetworkRequest request = createTokenRequest(QUrl(m_tokenEndpoint));
QNetworkReply *reply = m_networkManager->post(request, params.toString(QUrl::FullyEncoded).toUtf8()); QNetworkReply *reply = m_networkManager->post(request, params.toString(QUrl::FullyEncoded).toUtf8());
connect(reply, &QNetworkReply::finished, this, [this, reply]() { connect(reply, &QNetworkReply::finished, this, [this, reply]() {
onTokenReply(reply); handleTokenResponse(reply->readAll(), m_email, "gmail");
reply->deleteLater();
}); });
} }
void GmailAuthenticator::onTokenReply(QNetworkReply *reply)
{
if (reply->error() == QNetworkReply::NoError) {
QByteArray data = reply->readAll();
handleTokenResponse(data, m_email, "gmail");
} else {
QByteArray responseData = reply->readAll();
qWarning() << "[GmailAuthenticator] Token request failed:"
<< reply->errorString()
<< "Response:" << QString::fromUtf8(responseData);
emit authenticationFailed("Gmail token request failed: " + reply->errorString());
}
reply->deleteLater();
}
#include "gmailauthenticator.moc" #include "gmailauthenticator.moc"
+4 -29
View File
@@ -24,7 +24,7 @@ OutlookAuthenticator::OutlookAuthenticator(QObject *parent)
connect(m_callbackServer, &OAuthCallbackServer::codeReceived, connect(m_callbackServer, &OAuthCallbackServer::codeReceived,
this, &OutlookAuthenticator::onAuthCodeReceived); this, &OutlookAuthenticator::onAuthCodeReceived);
connect(m_callbackServer, &OAuthCallbackServer::errorOccurred, connect(m_callbackServer, &OAuthCallbackServer::errorOccurred,
this, [this](const QString &error) { [this](const QString &error) {
emit authenticationFailed("OAuth callback error: " + error); emit authenticationFailed("OAuth callback error: " + error);
}); });
} }
@@ -58,18 +58,7 @@ void OutlookAuthenticator::onAuthCodeReceived(const QString &code, const QString
m_callbackServer->stop(); m_callbackServer->stop();
QUrlQuery params; exchangeAuthorizationCode(code, m_redirectUri);
params.addQueryItem("code", code);
params.addQueryItem("client_id", m_clientId);
params.addQueryItem("client_secret", m_clientSecret);
params.addQueryItem("redirect_uri", m_redirectUri);
params.addQueryItem("grant_type", "authorization_code");
QNetworkRequest request = createTokenRequest(QUrl(m_tokenEndpoint));
QNetworkReply *reply = m_networkManager->post(request, params.toString(QUrl::FullyEncoded).toUtf8());
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
onTokenReply(reply);
});
} }
void OutlookAuthenticator::refreshToken(const Account &account) void OutlookAuthenticator::refreshToken(const Account &account)
@@ -86,23 +75,9 @@ void OutlookAuthenticator::refreshToken(const Account &account)
QNetworkRequest request = createTokenRequest(QUrl(m_tokenEndpoint)); QNetworkRequest request = createTokenRequest(QUrl(m_tokenEndpoint));
QNetworkReply *reply = m_networkManager->post(request, params.toString(QUrl::FullyEncoded).toUtf8()); QNetworkReply *reply = m_networkManager->post(request, params.toString(QUrl::FullyEncoded).toUtf8());
connect(reply, &QNetworkReply::finished, this, [this, reply]() { connect(reply, &QNetworkReply::finished, this, [this, reply]() {
onTokenReply(reply); handleTokenResponse(reply->readAll(), m_email, "outlook");
reply->deleteLater();
}); });
} }
void OutlookAuthenticator::onTokenReply(QNetworkReply *reply)
{
if (reply->error() == QNetworkReply::NoError) {
QByteArray data = reply->readAll();
handleTokenResponse(data, m_email, "outlook");
} else {
QByteArray responseData = reply->readAll();
qWarning() << "[OutlookAuthenticator] Token request failed:"
<< reply->errorString()
<< "Response:" << QString::fromUtf8(responseData);
emit authenticationFailed("Outlook token request failed: " + reply->errorString());
}
reply->deleteLater();
}
#include "outlookauthenticator.moc" #include "outlookauthenticator.moc"
+6
View File
@@ -59,6 +59,12 @@ void MailListView::setupUI() {
m_tableView->setModel(m_proxyModel); m_tableView->setModel(m_proxyModel);
connect(m_tableView, &QTableView::clicked, this, &MailListView::onRowSelected); connect(m_tableView, &QTableView::clicked, this, &MailListView::onRowSelected);
connect(m_tableView, &QTableView::doubleClicked, this, [this](const QModelIndex &index) {
if (!index.isValid()) return;
QModelIndex sourceIndex = m_proxyModel->mapToSource(index);
int mailId = sourceIndex.data(EmailListModel::IdRole).toInt();
emit emailOpenRequested(mailId);
});
layout->addWidget(m_tableView); layout->addWidget(m_tableView);
} }
+1
View File
@@ -21,6 +21,7 @@ public:
signals: signals:
void emailSelected(int mailId); void emailSelected(int mailId);
void emailOpenRequested(int mailId);
void composeRequested(); void composeRequested();
private slots: private slots:
+27
View File
@@ -164,12 +164,21 @@ void MainMainWindow::setupMailPage() {
m_mailListView = new MailListView(); m_mailListView = new MailListView();
connect(m_mailListView, &MailListView::emailSelected, this, &MainMainWindow::onEmailSelected); connect(m_mailListView, &MailListView::emailSelected, this, &MainMainWindow::onEmailSelected);
connect(m_mailListView, &MailListView::composeRequested, this, &MainMainWindow::onComposeRequested); connect(m_mailListView, &MailListView::composeRequested, this, &MainMainWindow::onComposeRequested);
connect(m_mailListView, &MailListView::emailOpenRequested, this, [this](int mailId) {
openMailInIndependentWindow(mailId);
});
m_folderSplitter->addWidget(m_mailListView); m_folderSplitter->addWidget(m_mailListView);
// Reader // Reader
m_emailViewer = new ReaderView(); m_emailViewer = new ReaderView();
m_emailViewer->setMinimumWidth(350); m_emailViewer->setMinimumWidth(350);
connect(m_emailViewer, &ReaderView::replyRequested, this, &MainMainWindow::onReaderReplyRequested); connect(m_emailViewer, &ReaderView::replyRequested, this, &MainMainWindow::onReaderReplyRequested);
connect(m_emailViewer, &ReaderView::detachRequested, this, [this]() {
std::optional<MailItem> currentItem = MailItemDao::findById(m_emailModel->getCurrentMailId());
if (currentItem) {
openMailInIndependentWindow(currentItem->id());
}
});
m_folderSplitter->addWidget(m_emailViewer); m_folderSplitter->addWidget(m_emailViewer);
// Default sizes: folder 240, list 380, reader flex // Default sizes: folder 240, list 380, reader flex
@@ -248,6 +257,24 @@ void MainMainWindow::onNewMessage() {
switchToPage(PageCompose); switchToPage(PageCompose);
} }
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 = addToolBar("Main Toolbar");
m_toolBar->setMovable(false); m_toolBar->setMovable(false);
+1
View File
@@ -34,6 +34,7 @@ private slots:
void onComposeRequested(); void onComposeRequested();
void onReaderReplyRequested(const MailItem *item); void onReaderReplyRequested(const MailItem *item);
void onNewMessage(); void onNewMessage();
void openMailInIndependentWindow(int mailId);
private: private:
void setupUI(); void setupUI();
+6
View File
@@ -41,8 +41,12 @@ void ReaderView::setupUI() {
m_deleteButton = new QPushButton("Delete"); m_deleteButton = new QPushButton("Delete");
m_deleteButton->setStyleSheet("color: red;"); m_deleteButton->setStyleSheet("color: red;");
QPushButton *m_detachButton = new QPushButton("独立 (Independent)");
m_detachButton->setToolTip("Open in separate window");
actionsLayout->addWidget(m_replyButton); actionsLayout->addWidget(m_replyButton);
actionsLayout->addWidget(m_forwardButton); actionsLayout->addWidget(m_forwardButton);
actionsLayout->addWidget(m_detachButton);
actionsLayout->addStretch(); actionsLayout->addStretch();
actionsLayout->addWidget(m_deleteButton); actionsLayout->addWidget(m_deleteButton);
@@ -59,6 +63,8 @@ void ReaderView::setupUI() {
connect(m_replyButton, &QPushButton::clicked, [this]() { connect(m_replyButton, &QPushButton::clicked, [this]() {
if (m_bodyViewer->toPlainText().isEmpty()) return; if (m_bodyViewer->toPlainText().isEmpty()) return;
}); });
connect(m_detachButton, &QPushButton::clicked, this, &ReaderView::detachRequested);
} }
void ReaderView::setMailItem(const MailItem* item) { void ReaderView::setMailItem(const MailItem* item) {
+2
View File
@@ -21,6 +21,8 @@ signals:
void replyRequested(const MailItem* item); void replyRequested(const MailItem* item);
void forwardRequested(const MailItem* item); void forwardRequested(const MailItem* item);
void deleteRequested(const MailItem* item); void deleteRequested(const MailItem* item);
void detachRequested();
private: private:
void setupUI(); void setupUI();