Complete phases 6 and 7: Notifications & System Tray, Background Sync & Scheduling
This commit is contained in:
+34
-4
@@ -7,13 +7,34 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
# Find Qt components
|
# Find Qt components
|
||||||
find_package(Qt6 COMPONENTS Core Gui Widgets Network Sql WebEngineWidgets Qml Multimedia REQUIRED)
|
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 optional libraries (example: libetpan, gmime)
|
||||||
# find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig)
|
||||||
# pkg_check_modules(LIBETPAN REQUIRED libetpan)
|
pkg_check_modules(GMIME gmime-3.0)
|
||||||
# pkg_check_modules(GMIME REQUIRED gmime-2.6)
|
|
||||||
|
|
||||||
# Include directories
|
# Include directories
|
||||||
include_directories(${PROJECT_SOURCE_DIR}/src)
|
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
|
# Enable automatic moc, uic, rcc
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
@@ -44,14 +65,23 @@ set(SRC_FILES
|
|||||||
src/core/eventbus.cpp
|
src/core/eventbus.cpp
|
||||||
src/utils/notificationmanager.cpp
|
src/utils/notificationmanager.cpp
|
||||||
src/core/emailmanager.cpp
|
src/core/emailmanager.cpp
|
||||||
|
src/core/synchronizerprovider.cpp
|
||||||
src/syncscheduler.cpp
|
src/syncscheduler.cpp
|
||||||
|
src/core/accountsetupdialoglauncher.cpp
|
||||||
|
src/ui/dialogs/accountsetupdialog.cpp
|
||||||
|
resources.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
# Executable
|
# Executable
|
||||||
add_executable(wino-mail-qt ${SRC_FILES})
|
add_executable(wino-mail-qt ${SRC_FILES})
|
||||||
|
|
||||||
# Link Qt
|
# 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
|
# If using external libs
|
||||||
# target_link_libraries(wino-mail-qt PRIVATE ${LIBETPAN_LIBRARIES} ${GMIME_LIBRARIES})
|
# target_link_libraries(wino-mail-qt PRIVATE ${LIBETPAN_LIBRARIES} ${GMIME_LIBRARIES})
|
||||||
|
|||||||
@@ -2,5 +2,13 @@
|
|||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
<file>resources/qml/main.qml</file>
|
<file>resources/qml/main.qml</file>
|
||||||
<file>resources/qml/Shell.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>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
@@ -9,11 +9,183 @@ Item {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "#fafafa"
|
color: "#fafafa"
|
||||||
Text {
|
|
||||||
text: qsTr("Settings Page - Placeholder")
|
ColumnLayout {
|
||||||
anchors.centerIn: parent
|
anchors.fill: parent
|
||||||
font.pointSize: 16
|
anchors.margins: 20
|
||||||
color: "#666"
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ bool Translator::loadLanguage(const QString& langCode)
|
|||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
m_translations.clear();
|
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);
|
QFile file(filePath);
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
qWarning() << "Failed to open translation file:" << filePath;
|
qWarning() << "Failed to open translation file:" << filePath;
|
||||||
|
|||||||
+21
-1
@@ -6,6 +6,7 @@
|
|||||||
#include "core/emailmanager.h"
|
#include "core/emailmanager.h"
|
||||||
#include "syncscheduler.h"
|
#include "syncscheduler.h"
|
||||||
#include "utils/notificationmanager.h"
|
#include "utils/notificationmanager.h"
|
||||||
|
#include "core/accountsetupdialoglauncher.h"
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
@@ -31,13 +32,17 @@ int main(int argc, char *argv[])
|
|||||||
NotificationManager notificationManager(&app);
|
NotificationManager notificationManager(&app);
|
||||||
notificationManager.initialize(); // Initialize Qt components after QApplication is ready
|
notificationManager.initialize(); // Initialize Qt components after QApplication is ready
|
||||||
|
|
||||||
|
// Create AccountSetupDialogLauncher to expose to QML
|
||||||
|
AccountSetupDialogLauncher accountSetupDialogLauncher(&app);
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
engine.rootContext()->setContextProperty("translator", static_cast<QObject*>(&translator));
|
engine.rootContext()->setContextProperty("translator", static_cast<QObject*>(&translator));
|
||||||
engine.rootContext()->setContextProperty("emailManager", &emailManager);
|
engine.rootContext()->setContextProperty("emailManager", &emailManager);
|
||||||
engine.rootContext()->setContextProperty("syncScheduler", &syncScheduler);
|
engine.rootContext()->setContextProperty("syncScheduler", &syncScheduler);
|
||||||
engine.rootContext()->setContextProperty("notificationManager", ¬ificationManager);
|
engine.rootContext()->setContextProperty("notificationManager", ¬ificationManager);
|
||||||
|
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,
|
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
|
||||||
&app, [url](QObject *obj, const QUrl &objUrl) {
|
&app, [url](QObject *obj, const QUrl &objUrl) {
|
||||||
if (!obj && url == objUrl)
|
if (!obj && url == objUrl)
|
||||||
@@ -45,5 +50,20 @@ int main(int argc, char *argv[])
|
|||||||
}, Qt::QueuedConnection);
|
}, Qt::QueuedConnection);
|
||||||
engine.load(url);
|
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(¬ificationManager, &NotificationManager::showHideRequested,
|
||||||
|
[rootObject]() {
|
||||||
|
if (rootObject) {
|
||||||
|
bool visible = rootObject->property("visible").toBool();
|
||||||
|
rootObject->setProperty("visible", !visible);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
@@ -11,8 +11,8 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
#include "../core/events.h"
|
#include "../../core/events.h"
|
||||||
#include "../core/eventbus.h"
|
#include "../../core/eventbus.h"
|
||||||
|
|
||||||
GmailSynchronizer::GmailSynchronizer(QObject* parent)
|
GmailSynchronizer::GmailSynchronizer(QObject* parent)
|
||||||
: Synchronizer(parent),
|
: Synchronizer(parent),
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
#include "../core/events.h"
|
#include "../../core/events.h"
|
||||||
#include "../core/eventbus.h"
|
#include "../../core/eventbus.h"
|
||||||
|
|
||||||
OutlookSynchronizer::OutlookSynchronizer(QObject* parent)
|
OutlookSynchronizer::OutlookSynchronizer(QObject* parent)
|
||||||
: Synchronizer(parent),
|
: Synchronizer(parent),
|
||||||
|
|||||||
+46
-21
@@ -2,9 +2,10 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include "db/dao/accountdao.h"
|
#include "db/dao/accountdao.h"
|
||||||
#include "../services/synchronizer.h"
|
#include "core/synchronizerprovider.h"
|
||||||
#include "../core/eventbus.h"
|
#include "services/synchronizer.h"
|
||||||
#include "../core/events.h"
|
#include "core/eventbus.h"
|
||||||
|
#include "core/events.h"
|
||||||
|
|
||||||
SyncScheduler::SyncScheduler(QObject *parent)
|
SyncScheduler::SyncScheduler(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
@@ -52,21 +53,45 @@ void SyncScheduler::onTimerTick()
|
|||||||
void SyncScheduler::performSync()
|
void SyncScheduler::performSync()
|
||||||
{
|
{
|
||||||
qDebug() << "SyncScheduler: Performing synchronization...";
|
qDebug() << "SyncScheduler: Performing synchronization...";
|
||||||
// Trigger a manual sync by publishing a SyncRequestedEvent or calling synchronizers directly.
|
bool syncPerformed = false;
|
||||||
// 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.
|
// Get all accounts
|
||||||
// Instead, we can invoke each synchronizer's syncFolder for all folders? That would be heavy.
|
QVector<Account> accounts = AccountDao::findAll();
|
||||||
// Following the plan, we want to trigger sync for all accounts.
|
for (const Account& account : accounts) {
|
||||||
// We'll create a SyncRequestedEvent and publish it.
|
// Get the synchronizer for this account
|
||||||
// However, we don't have such event defined. We can extend events.h, but to keep it simple,
|
Synchronizer* synchronizer = SynchronizerProvider::instance().getSynchronizer(QString::number(account.id()));
|
||||||
// we can just call the synchronizers via a central place? Not ideal.
|
if (!synchronizer) {
|
||||||
// Given time, we'll just log and later implement.
|
qWarning() << "SyncScheduler: No synchronizer found for account ID" << account.id();
|
||||||
qDebug() << "SyncScheduler: Sync trigger would happen here.";
|
continue;
|
||||||
// Update last sync timestamp
|
}
|
||||||
QSettings settings;
|
|
||||||
qint64 now = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
|
// Optionally, we could initialize the synchronizer if not already initialized.
|
||||||
settings.setValue("lastSyncTimestamp", now);
|
// However, we assume it's already initialized and registered (e.g., when account was added).
|
||||||
qDebug() << "SyncScheduler: Updated last sync timestamp to" << now;
|
// 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
|
bool SyncScheduler::shouldRunSync() const
|
||||||
@@ -80,9 +105,9 @@ bool SyncScheduler::shouldRunSync() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Check if current hour >= m_runHour (UTC? we'll use local time for simplicity)
|
// Check if current hour >= m_runHour (UTC? we'll use local time for simplicity)
|
||||||
QDateTime nowLocal = QDateTime::currentDateTime();
|
QDateTime nowUtc = QDateTime::currentDateTimeUtc();
|
||||||
if (nowLocal.time().hour() < m_runHour) {
|
if (nowUtc.time().hour() < m_runHour) {
|
||||||
qDebug() << "SyncScheduler: Current hour" << nowLocal.time().hour() << "is less than run hour" << m_runHour;
|
qDebug() << "SyncScheduler: Current UTC hour" << nowUtc.time().hour() << "is less than run hour" << m_runHour;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user