Compare commits

...

3 Commits

10 changed files with 624 additions and 41 deletions
+35 -5
View File
@@ -7,13 +7,34 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find Qt components
find_package(Qt6 COMPONENTS Core Gui Widgets Network Sql WebEngineWidgets Qml Multimedia REQUIRED)
# Optional: DTK Widgets for Deepin look
option(USE_DTKWIDGET "Use DTK Widgets for Deepin look" ON)
if(USE_DTKWIDGET)
find_package(PkgConfig)
pkg_check_modules(DTKWIDGET dtkwidget)
if(DTKWIDGET_FOUND)
message(STATUS "Found DTKWidget: ${DTKWIDGET_VERSION}")
add_definitions(-DDTKWIDGET_FOUND)
include_directories(${DTKWIDGET_INCLUDE_DIRS})
set(DTKWIDGET_LIBS ${DTKWIDGET_LIBRARIES})
else()
message(WARNING "DTKWidget not found, using fallback Qt widgets")
set(USE_DTKWIDGET OFF)
endif()
endif()
# Find optional libraries (example: libetpan, gmime)
# find_package(PkgConfig REQUIRED)
# pkg_check_modules(LIBETPAN REQUIRED libetpan)
# pkg_check_modules(GMIME REQUIRED gmime-2.6)
find_package(PkgConfig)
pkg_check_modules(GMIME gmime-3.0)
# Include directories
include_directories(${PROJECT_SOURCE_DIR}/src)
if(USE_DTKWIDGET AND DTKWIDGET_FOUND)
include_directories(${DTKWIDGET_INCLUDE_DIRS})
endif()
if(GMIME_FOUND)
include_directories(${GMIME_INCLUDE_DIRS})
endif()
# Enable automatic moc, uic, rcc
set(CMAKE_AUTOMOC ON)
@@ -44,18 +65,27 @@ set(SRC_FILES
src/core/eventbus.cpp
src/utils/notificationmanager.cpp
src/core/emailmanager.cpp
src/core/synchronizerprovider.cpp
src/syncscheduler.cpp
src/core/accountsetupdialoglauncher.cpp
src/ui/dialogs/accountsetupdialog.cpp
resources.qrc
)
# Executable
add_executable(wino-mail-qt ${SRC_FILES})
# Link Qt
target_link_libraries(wino-mail-qt PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Sql Qt6::WebEngineWidgets Qt6::Qml Qt6::Multimedia)
target_link_libraries(wino-mail-qt PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Sql Qt6::WebEngineWidgets Qt6::Qml Qt6::Quick Qt6::QuickControls2 Qt6::Multimedia)
# Link DTKWidget if found
if(USE_DTKWIDGET AND DTKWIDGET_FOUND)
target_link_libraries(wino-mail-qt PRIVATE ${DTKWIDGET_LIBS})
endif()
# If using external libs
# target_link_libraries(wino-mail-qt PRIVATE ${LIBETPAN_LIBRARIES} ${GMIME_LIBRARIES})
# target_include_directories(wino-mail-qt PRIVATE ${LIBETPAN_INCLUDE_DIRS} ${GMIME_INCLUDE_DIRS})
# Install (optional)
install(TARGETS wino-mail-qt DESTINATION bin)
install(TARGETS wino-mail-qt DESTINATION bin)
+9 -1
View File
@@ -2,5 +2,13 @@
<qresource prefix="/">
<file>resources/qml/main.qml</file>
<file>resources/qml/Shell.qml</file>
<file>resources/qml/MailListPage.qml</file>
<file>resources/qml/ReaderPage.qml</file>
<file>resources/qml/ComposePage.qml</file>
<file>resources/qml/ContactsPage.qml</file>
<file>resources/qml/SettingsPage.qml</file>
<file>resources/qml/CalendarPage.qml</file>
<file>resources/qml/models/FolderModel.qml</file>
<file>resources/translations/en_US/resources.json</file>
</qresource>
</RCC>
</RCC>
+177 -5
View File
@@ -9,11 +9,183 @@ Item {
Rectangle {
anchors.fill: parent
color: "#fafafa"
Text {
text: qsTr("Settings Page - Placeholder")
anchors.centerIn: parent
font.pointSize: 16
color: "#666"
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 20
// Accounts section
GroupBox {
title: qsTr("Email Accounts")
Layout.fillWidth: true
Layout.preferredHeight: 200
ColumnLayout {
anchors.fill: parent
anchors.margins: 15
spacing: 10
// Accounts list placeholder
ListView {
id: accountsListView
Layout.fillWidth: true
Layout.fillHeight: true
model: 0 // Placeholder - would be connected to actual accounts model
delegate: Item {
height: 60
width: parent.width
Rectangle {
anchors.fill: parent
color: "#ffffff"
radius: 4
border.color: "#e0e0e0"
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
Image {
source: "account-outline.svg"
width: 24
height: 24
color: "#1976D2"
}
ColumnLayout {
Label {
text: "example@email.com"
font.bold: true
color: "#333"
}
Label {
text: "Outlook • john.doe@example.com"
font.pointSize: 10
color: "#666"
}
}
Item {
Layout.fillWidth: true
}
IconButton {
icon: "edit"
iconSize: 20
color: "#666"
onClicked: {
// Would open edit account dialog
}
}
IconButton {
icon: "delete"
iconSize: 20
color: "#f44336"
onClicked: {
// Would delete account
}
}
}
}
}
}
// Add account button
RowLayout {
Layout.fillWidth: true
Button {
text: qsTr("Add Email Account")
icon: "plus"
Layout.fillWidth: true
height: 40
onClicked: {
accountSetupDialogLauncher.showDialog()
}
}
}
}
}
// General settings section
GroupBox {
title: qsTr("General Settings")
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
anchors.margins: 15
spacing: 15
Switch {
text: qsTr("Start application on login")
checked: true
}
Switch {
text: qsTr("Enable notifications")
checked: true
}
Switch {
text: qsTr("Minimize to tray on close")
checked: true
}
Label {
text: qsTr("Sync Interval:")
font.bold: true
}
RowLayout {
ComboBox {
width: 100
model: [15, 30, 60, 120]
textRole: "display"
currentIndex: 1 // 30 minutes
}
Label {
text: qsTr("minutes")
}
}
}
}
// Appearance section
GroupBox {
title: qsTr("Appearance")
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
anchors.margins: 15
spacing: 15
Label {
text: qsTr("Theme:")
font.bold: true
}
RowLayout {
RadioButton {
text: qsTr("Light")
checked: true
}
RadioButton {
text: qsTr("Dark")
}
RadioButton {
text: qsTr("System")
}
}
Switch {
text: qsTr("Use Deepin theme")
checked: true
}
}
}
}
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ bool Translator::loadLanguage(const QString& langCode)
QMutexLocker locker(&m_mutex);
m_translations.clear();
QString filePath = QStringLiteral("resources/translations/%1/resources.json").arg(langCode);
QString filePath = QStringLiteral(":/resources/translations/%1/resources.json").arg(langCode);
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "Failed to open translation file:" << filePath;
+21 -1
View File
@@ -6,6 +6,7 @@
#include "core/emailmanager.h"
#include "syncscheduler.h"
#include "utils/notificationmanager.h"
#include "core/accountsetupdialoglauncher.h"
int main(int argc, char *argv[])
{
@@ -31,13 +32,17 @@ int main(int argc, char *argv[])
NotificationManager notificationManager(&app);
notificationManager.initialize(); // Initialize Qt components after QApplication is ready
// Create AccountSetupDialogLauncher to expose to QML
AccountSetupDialogLauncher accountSetupDialogLauncher(&app);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("translator", static_cast<QObject*>(&translator));
engine.rootContext()->setContextProperty("emailManager", &emailManager);
engine.rootContext()->setContextProperty("syncScheduler", &syncScheduler);
engine.rootContext()->setContextProperty("notificationManager", &notificationManager);
engine.rootContext()->setContextProperty("accountSetupDialogLauncher", &accountSetupDialogLauncher);
const QUrl url(QStringLiteral("qrc:/main.qml"));
const QUrl url(QStringLiteral("qrc:/resources/qml/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
@@ -45,5 +50,20 @@ int main(int argc, char *argv[])
}, Qt::QueuedConnection);
engine.load(url);
// After loading QML, get the root object to connect the notificationManager's showHideRequested signal
QObject *rootObject = nullptr;
if (!engine.rootObjects().isEmpty()) {
rootObject = engine.rootObjects().first();
}
// Connect the notificationManager's showHideRequested signal to toggle the root object's visibility
QObject::connect(&notificationManager, &NotificationManager::showHideRequested,
[rootObject]() {
if (rootObject) {
bool visible = rootObject->property("visible").toBool();
rootObject->setProperty("visible", !visible);
}
});
return app.exec();
}
+62
View File
@@ -10,6 +10,9 @@
#include <QUrlQuery>
#include <QDateTime>
#include <QTimer>
#include <QEventLoop>
#include "../../core/events.h"
#include "../../core/eventbus.h"
GmailSynchronizer::GmailSynchronizer(QObject* parent)
: Synchronizer(parent),
@@ -41,6 +44,14 @@ bool GmailSynchronizer::initialize(const Account& account)
return false;
}
// Publish AccountConnectedEvent
WinoMail::Events::AccountConnectedEvent event;
event.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
event.timestamp = QDateTime::currentDateTimeUtc();
event.accountId = account.id();
event.provider = "gmail";
PUBLISH(event);
qDebug() << "Gmail Synchronizer initialized for account:" << account.email();
return true;
}
@@ -56,6 +67,15 @@ bool GmailSynchronizer::syncFolder(const Folder& folder)
}
qDebug() << "Starting Gmail sync for folder (label):" << folder.name();
// Publish SyncStartedEvent
WinoMail::Events::SyncStartedEvent syncStartEvent;
syncStartEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
syncStartEvent.timestamp = QDateTime::currentDateTimeUtc();
syncStartEvent.accountId = m_account.id();
syncStartEvent.folderId = QString::number(folder.id());
PUBLISH(syncStartEvent);
emit folderSyncStarted(QString::number(folder.id()));
// Obtener elementos de correo desde Gmail API
@@ -68,10 +88,27 @@ bool GmailSynchronizer::syncFolder(const Folder& folder)
if (!items.isEmpty()) {
for (const MailItem& item : items) {
emit mailItemAdded(item);
// Publish MailItemAddedEvent
WinoMail::Events::MailItemAddedEvent mailEvent;
mailEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
mailEvent.timestamp = QDateTime::currentDateTimeUtc();
mailEvent.item = item;
PUBLISH(mailEvent);
}
}
emit folderSyncFinished(QString::number(folder.id()), true);
// Publish SyncFinishedEvent
WinoMail::Events::SyncFinishedEvent syncFinishEvent;
syncFinishEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
syncFinishEvent.timestamp = QDateTime::currentDateTimeUtc();
syncFinishEvent.accountId = m_account.id();
syncFinishEvent.folderId = QString::number(folder.id());
syncFinishEvent.success = true;
syncFinishEvent.errorMessage = "";
PUBLISH(syncFinishEvent);
return true;
}
@@ -154,6 +191,14 @@ bool GmailSynchronizer::appendMailItem(const QString& folderId, const MailItem&
// Por ahora, simulamos éxito
emit mailItemAdded(item);
// Publish MailItemAddedEvent
WinoMail::Events::MailItemAddedEvent mailEvent;
mailEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
mailEvent.timestamp = QDateTime::currentDateTimeUtc();
mailEvent.item = item;
PUBLISH(mailEvent);
return true;
}
@@ -182,6 +227,14 @@ bool GmailSynchronizer::updateMailItemFlags(const QString& folderId,
updatedItem.setFlagged(flagged);
emit mailItemUpdated(updatedItem);
// Publish MailItemUpdatedEvent
WinoMail::Events::MailItemUpdatedEvent updateEvent;
updateEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
updateEvent.timestamp = QDateTime::currentDateTimeUtc();
updateEvent.item = updatedItem;
updateEvent.changedFields = QStringList() << "read" << "flagged"; // Simplified
PUBLISH(updateEvent);
return true;
}
@@ -202,6 +255,15 @@ bool GmailSynchronizer::deleteMailItem(const QString& folderId,
// Por ahora, simulamos éxito
emit mailItemRemoved(itemUid);
// Publish MailItemRemovedEvent
WinoMail::Events::MailItemRemovedEvent removeEvent;
removeEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
removeEvent.timestamp = QDateTime::currentDateTimeUtc();
removeEvent.itemUid = itemUid;
removeEvent.folderId = folderId.toInt();
PUBLISH(removeEvent);
return true;
}
+122 -3
View File
@@ -10,6 +10,9 @@
#include <QUrlQuery>
#include <QDateTime>
#include <QTimer>
#include <QEventLoop>
#include "../../core/events.h"
#include "../../core/eventbus.h"
OutlookSynchronizer::OutlookSynchronizer(QObject* parent)
: Synchronizer(parent),
@@ -41,6 +44,14 @@ bool OutlookSynchronizer::initialize(const Account& account)
return false;
}
// Publish AccountConnectedEvent
WinoMail::Events::AccountConnectedEvent event;
event.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
event.timestamp = QDateTime::currentDateTimeUtc();
event.accountId = account.id();
event.provider = "outlook";
PUBLISH(event);
qDebug() << "Outlook Synchronizer initialized for account:" << account.email();
return true;
}
@@ -56,6 +67,15 @@ bool OutlookSynchronizer::syncFolder(const Folder& folder)
}
qDebug() << "Starting Outlook sync for folder:" << folder.name();
// Publish SyncStartedEvent
WinoMail::Events::SyncStartedEvent syncStartEvent;
syncStartEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
syncStartEvent.timestamp = QDateTime::currentDateTimeUtc();
syncStartEvent.accountId = m_account.id();
syncStartEvent.folderId = QString::number(folder.id());
PUBLISH(syncStartEvent);
emit folderSyncStarted(QString::number(folder.id()));
// Obtener elementos de correo desde Microsoft Graph
@@ -68,10 +88,27 @@ bool OutlookSynchronizer::syncFolder(const Folder& folder)
if (!items.isEmpty()) {
for (const MailItem& item : items) {
emit mailItemAdded(item);
// Publish MailItemAddedEvent
WinoMail::Events::MailItemAddedEvent mailEvent;
mailEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
mailEvent.timestamp = QDateTime::currentDateTimeUtc();
mailEvent.item = item;
PUBLISH(mailEvent);
}
}
emit folderSyncFinished(QString::number(folder.id()), true);
// Publish SyncFinishedEvent
WinoMail::Events::SyncFinishedEvent syncFinishEvent;
syncFinishEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
syncFinishEvent.timestamp = QDateTime::currentDateTimeUtc();
syncFinishEvent.accountId = m_account.id();
syncFinishEvent.folderId = QString::number(folder.id());
syncFinishEvent.success = true;
syncFinishEvent.errorMessage = "";
PUBLISH(syncFinishEvent);
return true;
}
@@ -150,6 +187,14 @@ bool OutlookSynchronizer::appendMailItem(const QString& folderId, const MailItem
// Por ahora, simulamos éxito
emit mailItemAdded(item);
// Publish MailItemAddedEvent
WinoMail::Events::MailItemAddedEvent mailEvent;
mailEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
mailEvent.timestamp = QDateTime::currentDateTimeUtc();
mailEvent.item = item;
PUBLISH(mailEvent);
return true;
}
@@ -178,6 +223,14 @@ bool OutlookSynchronizer::updateMailItemFlags(const QString& folderId,
updatedItem.setFlagged(flagged);
emit mailItemUpdated(updatedItem);
// Publish MailItemUpdatedEvent
WinoMail::Events::MailItemUpdatedEvent updateEvent;
updateEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
updateEvent.timestamp = QDateTime::currentDateTimeUtc();
updateEvent.item = updatedItem;
updateEvent.changedFields = QStringList() << "read" << "flagged"; // Simplified
PUBLISH(updateEvent);
return true;
}
@@ -198,6 +251,15 @@ bool OutlookSynchronizer::deleteMailItem(const QString& folderId,
// Por ahora, simulamos éxito
emit mailItemRemoved(itemUid);
// Publish MailItemRemovedEvent
WinoMail::Events::MailItemRemovedEvent removeEvent;
removeEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
removeEvent.timestamp = QDateTime::currentDateTimeUtc();
removeEvent.itemUid = itemUid;
removeEvent.folderId = folderId.toInt();
PUBLISH(removeEvent);
return true;
}
@@ -273,9 +335,66 @@ bool OutlookSynchronizer::refreshAccessToken()
// En una implementación real, aquí usaríamos el refresh token
// para obtener un nuevo access token desde Azure AD
qWarning() << "Token refresh not implemented - using stub";
// Simulamos éxito por ahora
return !m_refreshToken.isEmpty();
if (m_refreshToken.isEmpty()) {
qWarning() << "No refresh token available for Outlook";
return false;
}
// TODO: These should come from secure configuration, not hardcoded
const QString clientId = "YOUR_CLIENT_ID_HERE"; // Replace with actual client ID
const QString clientSecret = "YOUR_CLIENT_SECRET_HERE"; // Replace with actual client secret
const QString tokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
QNetworkRequest networkRequest{QUrl(tokenUrl)};
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QUrlQuery postData;
postData.addQueryItem("client_id", clientId);
postData.addQueryItem("scope", "Mail.Read Mail.ReadWrite Mail.Send offline_access");
postData.addQueryItem("refresh_token", m_refreshToken);
postData.addQueryItem("grant_type", "refresh_token");
postData.addQueryItem("client_secret", clientSecret);
QNetworkReply* reply = m_networkManager->post(networkRequest, postData.toString(QUrl::FullyEncoded).toUtf8());
// Wait for reply synchronously for simplicity in this context
QEventLoop loop;
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Outlook token refresh failed:" << reply->errorString();
reply->deleteLater();
return false;
}
QByteArray responseData = reply->readAll();
reply->deleteLater();
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(responseData, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Failed to parse Outlook token response:" << parseError.errorString();
return false;
}
QJsonObject obj = doc.object();
if (!obj.contains("access_token")) {
qWarning() << "No access_token in Outlook token response";
return false;
}
m_accessToken = obj["access_token"].toString();
int expiresIn = obj["expires_in"].toInt(3600); // default 1 hour
m_tokenExpires = QDateTime::currentDateTimeUtc().addSecs(expiresIn);
// Update refresh token if provided (sometimes rotation is used)
if (obj.contains("refresh_token")) {
m_refreshToken = obj["refresh_token"].toString();
}
qDebug() << "Outlook access token refreshed successfully, expires in" << expiresIn << "seconds";
return true;
}
QString OutlookSynchronizer::getDeltaLink(const QString& folderId) const
+48 -18
View File
@@ -1,5 +1,11 @@
#include "syncscheduler.h"
#include <QDebug>
#include <QSettings>
#include "db/dao/accountdao.h"
#include "core/synchronizerprovider.h"
#include "services/synchronizer.h"
#include "core/eventbus.h"
#include "core/events.h"
SyncScheduler::SyncScheduler(QObject *parent)
: QObject(parent)
@@ -47,21 +53,45 @@ void SyncScheduler::onTimerTick()
void SyncScheduler::performSync()
{
qDebug() << "SyncScheduler: Performing synchronization...";
// Trigger a manual sync by publishing a SyncRequestedEvent or calling synchronizers directly.
// For now, we'll publish a generic event that synchronizers can listen to.
// We'll create a simple event; but we don't have a specific event for manual sync trigger.
// Instead, we can invoke each synchronizer's syncFolder for all folders? That would be heavy.
// Following the plan, we want to trigger sync for all accounts.
// We'll create a SyncRequestedEvent and publish it.
// However, we don't have such event defined. We can extend events.h, but to keep it simple,
// we can just call the synchronizers via a central place? Not ideal.
// Given time, we'll just log and later implement.
qDebug() << "SyncScheduler: Sync trigger would happen here.";
// Update last sync timestamp
QSettings settings;
qint64 now = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
settings.setValue("lastSyncTimestamp", now);
qDebug() << "SyncScheduler: Updated last sync timestamp to" << now;
bool syncPerformed = false;
// Get all accounts
QVector<Account> accounts = AccountDao::findAll();
for (const Account& account : accounts) {
// Get the synchronizer for this account
Synchronizer* synchronizer = SynchronizerProvider::instance().getSynchronizer(QString::number(account.id()));
if (!synchronizer) {
qWarning() << "SyncScheduler: No synchronizer found for account ID" << account.id();
continue;
}
// Optionally, we could initialize the synchronizer if not already initialized.
// However, we assume it's already initialized and registered (e.g., when account was added).
// If we want to be safe, we can try to initialize it here.
// But note: the synchronizer might have been initialized with a different account instance?
// We'll skip initialization for now and rely on the synchronizer being ready.
// Get folders for this account and sync each one
QVector<Folder> folders = synchronizer->getFolders();
for (const Folder& folder : folders) {
qDebug() << "SyncScheduler: Syncing folder" << folder.name() << "for account ID" << account.id();
bool success = synchronizer->syncFolder(folder);
if (!success) {
qWarning() << "SyncScheduler: Failed to sync folder" << folder.name() << "for account ID" << account.id();
}
syncPerformed = true;
}
}
if (syncPerformed) {
// Update last sync timestamp only if we actually performed a sync
QSettings settings;
qint64 now = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
settings.setValue("lastSyncTimestamp", now);
qDebug() << "SyncScheduler: Updated last sync timestamp to" << now;
} else {
qDebug() << "SyncScheduler: No accounts found or no synchronizers available.";
}
}
bool SyncScheduler::shouldRunSync() const
@@ -75,9 +105,9 @@ bool SyncScheduler::shouldRunSync() const
return false;
}
// Check if current hour >= m_runHour (UTC? we'll use local time for simplicity)
QDateTime nowLocal = QDateTime::currentDateTime();
if (nowLocal.time().hour() < m_runHour) {
qDebug() << "SyncScheduler: Current hour" << nowLocal.time().hour() << "is less than run hour" << m_runHour;
QDateTime nowUtc = QDateTime::currentDateTimeUtc();
if (nowUtc.time().hour() < m_runHour) {
qDebug() << "SyncScheduler: Current UTC hour" << nowUtc.time().hour() << "is less than run hour" << m_runHour;
return false;
}
return true;
+136 -7
View File
@@ -9,6 +9,7 @@ NotificationManager::NotificationManager(QObject *parent)
, m_showHideAction(nullptr)
, m_quitAction(nullptr)
, m_newMailSound(nullptr)
, m_errorSound(nullptr)
{
// Defer initialization to after QApplication is fully ready
// We'll initialize in a separate method called from main after the event loop starts?
@@ -22,8 +23,8 @@ NotificationManager::~NotificationManager()
if (m_trayMenu) {
delete m_trayMenu;
}
// Note: m_trayIcon, m_showHideAction, m_quitAction, m_newMailSound are children of this or m_trayMenu?
// Actually, m_trayIcon and m_newMailSound have 'this' as parent, so they will be deleted automatically.
// Note: m_trayIcon, m_showHideAction, m_quitAction, m_newMailSound, m_errorSound are children of this or m_trayMenu?
// Actually, m_trayIcon and m_newMailSound, m_errorSound have 'this' as parent, so they will be deleted automatically.
// m_showHideAction and m_quitAction have m_trayMenu as parent, so they will be deleted when m_trayMenu is deleted.
}
@@ -31,10 +32,11 @@ void NotificationManager::initialize()
{
// Initialize the tray icon and related objects
m_trayIcon = new QSystemTrayIcon(this);
m_trayMenu = new QMenu(this); // parent to NotificationManager so it gets deleted with us
m_trayMenu = new QMenu(); // parent to NotificationManager so it gets deleted with us
m_showHideAction = new QAction("Show/Hide", m_trayMenu);
m_quitAction = new QAction("Quit", m_trayMenu);
m_newMailSound = new QSoundEffect(this);
m_errorSound = new QSoundEffect(this);
setupTrayIcon();
setupConnections();
@@ -53,6 +55,9 @@ void NotificationManager::setupTrayIcon()
m_newMailSound->setSource(QUrl::fromLocalFile("/usr/share/sounds/freedesktop/stereo/message-new-instant.oga"));
m_newMailSound->setVolume(0.5);
m_errorSound->setSource(QUrl::fromLocalFile("/usr/share/sounds/freedesktop/stereo/dialog-error.oga"));
m_errorSound->setVolume(0.5);
}
void NotificationManager::setupConnections()
@@ -62,10 +67,31 @@ void NotificationManager::setupConnections()
connect(m_showHideAction, &QAction::triggered, this, &NotificationManager::onShowHideRequested);
connect(m_quitAction, &QAction::triggered, qApp, &QGuiApplication::quit);
// Subscribe to mail added events
// Subscribe to events
SUBSCRIBE(WinoMail::Events::MailItemAddedEvent, [this](const WinoMail::Events::MailItemAddedEvent& event) {
onMailItemAdded(event);
});
SUBSCRIBE(WinoMail::Events::MailItemRemovedEvent, [this](const WinoMail::Events::MailItemRemovedEvent& event) {
onMailItemRemoved(event);
});
SUBSCRIBE(WinoMail::Events::MailItemUpdatedEvent, [this](const WinoMail::Events::MailItemUpdatedEvent& event) {
onMailItemUpdated(event);
});
SUBSCRIBE(WinoMail::Events::AccountConnectedEvent, [this](const WinoMail::Events::AccountConnectedEvent& event) {
onAccountConnected(event);
});
SUBSCRIBE(WinoMail::Events::AccountDisconnectedEvent, [this](const WinoMail::Events::AccountDisconnectedEvent& event) {
onAccountDisconnected(event);
});
SUBSCRIBE(WinoMail::Events::SyncStartedEvent, [this](const WinoMail::Events::SyncStartedEvent& event) {
onSyncStarted(event);
});
SUBSCRIBE(WinoMail::Events::SyncFinishedEvent, [this](const WinoMail::Events::SyncFinishedEvent& event) {
onSyncFinished(event);
});
SUBSCRIBE(WinoMail::Events::ErrorEvent, [this](const WinoMail::Events::ErrorEvent& event) {
onErrorOccurred(event);
});
// Show tray icon
m_trayIcon->show();
@@ -85,6 +111,98 @@ void NotificationManager::onMailItemAdded(const WinoMail::Events::MailItemAddedE
playNewMailSound();
}
void NotificationManager::onMailItemRemoved(const WinoMail::Events::MailItemRemovedEvent& event)
{
qDebug() << "NotificationManager: Mail removed - UID:" << event.itemUid;
// Show a system tray notification
m_trayIcon->showMessage(
"Wino Mail - Correo eliminado",
QString("Correo con UID %1 eliminado").arg(event.itemUid),
QSystemTrayIcon::MessageIcon::Information,
3000 // 3 seconds
);
}
void NotificationManager::onMailItemUpdated(const WinoMail::Events::MailItemUpdatedEvent& event)
{
qDebug() << "NotificationManager: Mail updated - UID:" << event.item.id() << "Changed fields:" << event.changedFields;
// Show a system tray notification for significant changes (like read/unread)
if (event.changedFields.contains("read") || event.changedFields.contains("flagged")) {
m_trayIcon->showMessage(
"Wino Mail - Correo actualizado",
QString("Correo %1 actualizado").arg(event.item.subject()),
QSystemTrayIcon::MessageIcon::Information,
3000 // 3 seconds
);
}
}
void NotificationManager::onAccountConnected(const WinoMail::Events::AccountConnectedEvent& event)
{
qDebug() << "NotificationManager: Account connected - ID:" << event.accountId << "Provider:" << event.provider;
// Show a system tray notification
m_trayIcon->showMessage(
"Wino Mail - Cuenta conectada",
QString("Cuenta %1 conectada (%2)").arg(event.accountId).arg(event.provider),
QSystemTrayIcon::MessageIcon::Information,
3000 // 3 seconds
);
}
void NotificationManager::onAccountDisconnected(const WinoMail::Events::AccountDisconnectedEvent& event)
{
qDebug() << "NotificationManager: Account disconnected - ID:" << event.accountId << "Provider:" << event.provider << "Reason:" << event.reason;
// Show a system tray notification
m_trayIcon->showMessage(
"Wino Mail - Cuenta desconectada",
QString("Cuenta %1 desconectada (%2): %3").arg(event.accountId).arg(event.provider).arg(event.reason),
QSystemTrayIcon::MessageIcon::Warning,
5000 // 5 seconds
);
// Play error sound for disconnection
playErrorSound();
}
void NotificationManager::onSyncStarted(const WinoMail::Events::SyncStartedEvent& event)
{
qDebug() << "NotificationManager: Sync started - Account ID:" << event.accountId << "Folder ID:" << event.folderId;
// Optionally show a subtle notification or just log
// For now, we'll just log and not show a notification to avoid being too noisy
}
void NotificationManager::onSyncFinished(const WinoMail::Events::SyncFinishedEvent& event)
{
qDebug() << "NotificationManager: Sync finished - Account ID:" << event.accountId << "Folder ID:" << event.folderId << "Success:" << event.success;
if (!event.success) {
// Show error notification and play sound
m_trayIcon->showMessage(
"Wino Mail - Error de sincronización",
QString("Error al sincronizar cuenta %1, carpeta %2: %3").arg(event.accountId).arg(event.folderId).arg(event.errorMessage),
QSystemTrayIcon::MessageIcon::Critical,
5000 // 5 seconds
);
playErrorSound();
} else {
// Optionally show success notification
// m_trayIcon->showMessage("Wino Mail - Sincronización completada", QString("Sincronización completada para cuenta %1, carpeta %2").arg(event.accountId).arg(event.folderId), QSystemTrayIcon::MessageIcon::Information, 3000);
}
}
void NotificationManager::onErrorOccurred(const WinoMail::Events::ErrorEvent& event)
{
qDebug() << "NotificationManager: Error occurred - Source:" << event.source << "Message:" << event.message << "Critical:" << event.isCritical;
// Show a system tray notification for critical errors
if (event.isCritical) {
m_trayIcon->showMessage(
"Wino Mail - Error crítico",
QString("Error en %1: %2").arg(event.source).arg(event.message),
QSystemTrayIcon::MessageIcon::Critical,
5000 // 5 seconds
);
playErrorSound();
}
}
void NotificationManager::onTrayIconActivated(QSystemTrayIcon::ActivationReason reason)
{
if (reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::DoubleClick) {
@@ -94,9 +212,8 @@ void NotificationManager::onTrayIconActivated(QSystemTrayIcon::ActivationReason
void NotificationManager::onShowHideRequested()
{
// TODO: Implement actual show/hide of main window
// For now, just log
qDebug() << "NotificationManager: Show/Hide requested";
// Emit signal to toggle main window visibility
Q_EMIT showHideRequested();
}
void NotificationManager::onQuitRequested()
@@ -109,4 +226,16 @@ void NotificationManager::playNewMailSound()
if (m_newMailSound->isLoaded()) {
m_newMailSound->play();
}
}
void NotificationManager::playErrorSound()
{
if (m_errorSound->isLoaded()) {
m_errorSound->play();
}
}
void NotificationManager::showNotification(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon, int timeout)
{
m_trayIcon->showMessage(title, message, icon, timeout);
}
+13
View File
@@ -18,8 +18,18 @@ public:
~NotificationManager() override;
void initialize(); // Initialize Qt components after QApplication is ready
signals:
void showHideRequested();
private slots:
void onMailItemAdded(const WinoMail::Events::MailItemAddedEvent& event);
void onMailItemRemoved(const WinoMail::Events::MailItemRemovedEvent& event);
void onMailItemUpdated(const WinoMail::Events::MailItemUpdatedEvent& event);
void onAccountConnected(const WinoMail::Events::AccountConnectedEvent& event);
void onAccountDisconnected(const WinoMail::Events::AccountDisconnectedEvent& event);
void onSyncStarted(const WinoMail::Events::SyncStartedEvent& event);
void onSyncFinished(const WinoMail::Events::SyncFinishedEvent& event);
void onErrorOccurred(const WinoMail::Events::ErrorEvent& event);
void onTrayIconActivated(QSystemTrayIcon::ActivationReason reason);
void onShowHideRequested();
void onQuitRequested();
@@ -30,10 +40,13 @@ private:
QAction* m_showHideAction;
QAction* m_quitAction;
QSoundEffect* m_newMailSound;
QSoundEffect* m_errorSound;
void setupTrayIcon();
void setupConnections();
void playNewMailSound();
void playErrorSound();
void showNotification(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::Information, int timeout = 5000);
};
#endif // NOTIFICATIONMANAGER_H