diff --git a/CMakeLists.txt b/CMakeLists.txt
index be31e4b..99c8856 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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)
\ No newline at end of file
+install(TARGETS wino-mail-qt DESTINATION bin)
diff --git a/resources.qrc b/resources.qrc
index 1d39c46..b4e20eb 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -2,5 +2,13 @@
resources/qml/main.qml
resources/qml/Shell.qml
+ resources/qml/MailListPage.qml
+ resources/qml/ReaderPage.qml
+ resources/qml/ComposePage.qml
+ resources/qml/ContactsPage.qml
+ resources/qml/SettingsPage.qml
+ resources/qml/CalendarPage.qml
+ resources/qml/models/FolderModel.qml
+ resources/translations/en_US/resources.json
-
\ No newline at end of file
+
diff --git a/resources/qml/SettingsPage.qml b/resources/qml/SettingsPage.qml
index ad24efd..eaa6e1e 100644
--- a/resources/qml/SettingsPage.qml
+++ b/resources/qml/SettingsPage.qml
@@ -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
+ }
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/src/core/translator.cpp b/src/core/translator.cpp
index e3b97a2..2406f53 100644
--- a/src/core/translator.cpp
+++ b/src/core/translator.cpp
@@ -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;
diff --git a/src/main.cpp b/src/main.cpp
index 2dfc257..6b58f7b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -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(&translator));
engine.rootContext()->setContextProperty("emailManager", &emailManager);
engine.rootContext()->setContextProperty("syncScheduler", &syncScheduler);
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,
&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(¬ificationManager, &NotificationManager::showHideRequested,
+ [rootObject]() {
+ if (rootObject) {
+ bool visible = rootObject->property("visible").toBool();
+ rootObject->setProperty("visible", !visible);
+ }
+ });
+
return app.exec();
}
\ No newline at end of file
diff --git a/src/services/gmail/gmailsynchronizer.cpp b/src/services/gmail/gmailsynchronizer.cpp
index 63f5c55..7e934cb 100644
--- a/src/services/gmail/gmailsynchronizer.cpp
+++ b/src/services/gmail/gmailsynchronizer.cpp
@@ -11,8 +11,8 @@
#include
#include
#include
-#include "../core/events.h"
-#include "../core/eventbus.h"
+#include "../../core/events.h"
+#include "../../core/eventbus.h"
GmailSynchronizer::GmailSynchronizer(QObject* parent)
: Synchronizer(parent),
diff --git a/src/services/outlook/outlooksynchronizer.cpp b/src/services/outlook/outlooksynchronizer.cpp
index 9dbda72..f91a7d0 100644
--- a/src/services/outlook/outlooksynchronizer.cpp
+++ b/src/services/outlook/outlooksynchronizer.cpp
@@ -11,8 +11,8 @@
#include
#include
#include
-#include "../core/events.h"
-#include "../core/eventbus.h"
+#include "../../core/events.h"
+#include "../../core/eventbus.h"
OutlookSynchronizer::OutlookSynchronizer(QObject* parent)
: Synchronizer(parent),
diff --git a/src/syncscheduler.cpp b/src/syncscheduler.cpp
index 7bdbba7..1f145d5 100644
--- a/src/syncscheduler.cpp
+++ b/src/syncscheduler.cpp
@@ -2,9 +2,10 @@
#include
#include
#include "db/dao/accountdao.h"
-#include "../services/synchronizer.h"
-#include "../core/eventbus.h"
-#include "../core/events.h"
+#include "core/synchronizerprovider.h"
+#include "services/synchronizer.h"
+#include "core/eventbus.h"
+#include "core/events.h"
SyncScheduler::SyncScheduler(QObject *parent)
: QObject(parent)
@@ -52,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 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 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
@@ -80,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;