diff --git a/client/core/controllers/coreController.cpp b/client/core/controllers/coreController.cpp
index 878f4babc3..7800e32657 100644
--- a/client/core/controllers/coreController.cpp
+++ b/client/core/controllers/coreController.cpp
@@ -194,6 +194,9 @@ void CoreController::initControllers()
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
setQmlContextProperty("SettingsController", m_settingsUiController);
+ m_appearanceController = new AppearanceController(m_settingsController, this);
+ setQmlContextProperty("AppearanceController", m_appearanceController);
+
m_pageController = new PageController(m_serversController, m_settingsController, this);
setQmlContextProperty("PageController", m_pageController);
diff --git a/client/core/controllers/coreController.h b/client/core/controllers/coreController.h
index b8a3d16928..0ebec7bab3 100644
--- a/client/core/controllers/coreController.h
+++ b/client/core/controllers/coreController.h
@@ -11,6 +11,7 @@
#include "ui/controllers/api/subscriptionUiController.h"
#include "ui/controllers/api/apiNewsUiController.h"
+#include "ui/controllers/appearanceController.h"
#include "ui/controllers/appSplitTunnelingUiController.h"
#include "ui/controllers/allowedDnsUiController.h"
#include "ui/controllers/connectionUiController.h"
@@ -173,6 +174,7 @@ class CoreController : public QObject
AppSplitTunnelingUiController* m_appSplitTunnelingUiController;
AllowedDnsUiController* m_allowedDnsUiController;
LanguageUiController* m_languageUiController;
+ AppearanceController* m_appearanceController;
UpdateUiController* m_updateUiController;
SubscriptionUiController* m_subscriptionUiController;
diff --git a/client/core/controllers/settingsController.cpp b/client/core/controllers/settingsController.cpp
index aa79f0fc34..2a8112c673 100644
--- a/client/core/controllers/settingsController.cpp
+++ b/client/core/controllers/settingsController.cpp
@@ -351,6 +351,16 @@ void SettingsController::setAppLanguage(const QLocale &locale)
m_appSettingsRepository->setAppLanguage(locale);
}
+int SettingsController::getThemeMode() const
+{
+ return m_appSettingsRepository->getAppThemeMode();
+}
+
+void SettingsController::setThemeMode(int mode)
+{
+ m_appSettingsRepository->setAppThemeMode(mode);
+}
+
bool SettingsController::isPremV1MigrationReminderActive() const
{
return m_appSettingsRepository->isPremV1MigrationReminderActive();
diff --git a/client/core/controllers/settingsController.h b/client/core/controllers/settingsController.h
index 6edb410e4c..f3876ce0d9 100644
--- a/client/core/controllers/settingsController.h
+++ b/client/core/controllers/settingsController.h
@@ -89,6 +89,9 @@ class SettingsController : public QObject
QLocale getAppLanguage() const;
void setAppLanguage(const QLocale &locale);
+ int getThemeMode() const;
+ void setThemeMode(int mode);
+
signals:
void siteSplitTunnelingRouteModeChanged(RouteMode mode);
void siteSplitTunnelingToggled(bool enabled);
diff --git a/client/core/repositories/secureAppSettingsRepository.cpp b/client/core/repositories/secureAppSettingsRepository.cpp
index f8662fc3da..88d541b55b 100644
--- a/client/core/repositories/secureAppSettingsRepository.cpp
+++ b/client/core/repositories/secureAppSettingsRepository.cpp
@@ -49,6 +49,16 @@ void SecureAppSettingsRepository::setAppLanguage(QLocale locale)
emit appLanguageChanged(locale);
}
+int SecureAppSettingsRepository::getAppThemeMode() const
+{
+ return value("Conf/appTheme", 0).toInt();
+}
+
+void SecureAppSettingsRepository::setAppThemeMode(int mode)
+{
+ setValue("Conf/appTheme", mode);
+}
+
bool SecureAppSettingsRepository::useAmneziaDns() const
{
return value("Conf/useAmneziaDns", true).toBool();
diff --git a/client/core/repositories/secureAppSettingsRepository.h b/client/core/repositories/secureAppSettingsRepository.h
index a105688fad..17f47f192c 100644
--- a/client/core/repositories/secureAppSettingsRepository.h
+++ b/client/core/repositories/secureAppSettingsRepository.h
@@ -27,6 +27,9 @@ class SecureAppSettingsRepository : public QObject
QLocale getAppLanguage() const;
void setAppLanguage(QLocale locale);
+ int getAppThemeMode() const;
+ void setAppThemeMode(int mode);
+
bool useAmneziaDns() const;
void setUseAmneziaDns(bool enabled);
QStringList getAllowedDnsServers() const;
diff --git a/client/translations/amneziavpn_ar_EG.ts b/client/translations/amneziavpn_ar_EG.ts
index ae14dc895b..e0db2ade31 100644
--- a/client/translations/amneziavpn_ar_EG.ts
+++ b/client/translations/amneziavpn_ar_EG.ts
@@ -221,6 +221,42 @@
غير قادر علي إنشاء تكوين
+
+ ConnectionUiController
+
+
+ Connecting...
+ اتصال...
+
+
+
+ Connected
+ تم الاتصال
+
+
+
+ Reconnecting...
+ إعادة الاتصال...
+
+
+
+
+
+
+ Connect
+ اتصل
+
+
+
+ Disconnecting...
+ إنهاء الاتصال...
+
+
+
+ Preparing...
+ جاري التحضير...
+
+
ConnectionTypeSelectionDrawer
@@ -2091,6 +2127,49 @@ Already installed containers were found on the server. All installed containers
Cannot reset settings during active connection
لا يمكن إعادة ضبط الإعدادات اثناء تواجد اتصال فعال
+
+
+ Color theme
+ سمة الألوان
+
+
+
+ System
+ النظام
+
+
+
+ Light
+ فاتح
+
+
+
+ Dark
+ داكن
+
+
+
+ SelectThemeDrawer
+
+
+ Color theme
+ سمة الألوان
+
+
+
+ System
+ النظام
+
+
+
+ Light
+ فاتح
+
+
+
+ Dark
+ داكن
+
PageSettingsBackup
diff --git a/client/translations/amneziavpn_fa_IR.ts b/client/translations/amneziavpn_fa_IR.ts
index 9aa79e442c..a9dd38868e 100644
--- a/client/translations/amneziavpn_fa_IR.ts
+++ b/client/translations/amneziavpn_fa_IR.ts
@@ -223,6 +223,42 @@
قطع ارتباط...
+
+ ConnectionUiController
+
+
+ Connecting...
+ در حال ارتباط...
+
+
+
+ Connected
+ متصل
+
+
+
+ Reconnecting...
+ اتصال دوباره...
+
+
+
+
+
+
+ Connect
+ اتصال
+
+
+
+ Disconnecting...
+ قطع ارتباط...
+
+
+
+ Preparing...
+ در حال آمادهسازی...
+
+
ConnectionTypeSelectionDrawer
@@ -2174,6 +2210,49 @@ Already installed containers were found on the server. All installed containers
Cannot reset settings during active connection
نمیتوان تنظیمات را در حین اتصال فعال بازنشانی کرد.
+
+
+ Color theme
+ تم رنگی
+
+
+
+ System
+ سیستم
+
+
+
+ Light
+ روشن
+
+
+
+ Dark
+ تیره
+
+
+
+ SelectThemeDrawer
+
+
+ Color theme
+ تم رنگی
+
+
+
+ System
+ سیستم
+
+
+
+ Light
+ روشن
+
+
+
+ Dark
+ تیره
+
PageSettingsBackup
diff --git a/client/translations/amneziavpn_hi_IN.ts b/client/translations/amneziavpn_hi_IN.ts
index c6255cca34..36d8e2b5f6 100644
--- a/client/translations/amneziavpn_hi_IN.ts
+++ b/client/translations/amneziavpn_hi_IN.ts
@@ -206,6 +206,42 @@
सेटिंग्स सफलतापूर्वक अपडेट हो गईं
+
+ ConnectionUiController
+
+
+ Connecting...
+ कनेक्ट...
+
+
+
+ Connected
+ जुड़ा हुआ
+
+
+
+ Reconnecting...
+ पुनः कनेक्ट हो रहा है...
+
+
+
+
+
+
+ Connect
+ कनेक्ट
+
+
+
+ Disconnecting...
+ डिस्कनेक्ट हो रहा है...
+
+
+
+ Preparing...
+ तैयार कर रहे हैं...
+
+
ConnectionTypeSelectionDrawer
@@ -2091,6 +2127,49 @@ Already installed containers were found on the server. All installed containers
Cannot reset settings during active connection
सक्रिय कनेक्शन के दौरान सेटिंग्स रीसेट नहीं की जा सकतीं
+
+
+ Color theme
+ रंग थीम
+
+
+
+ System
+ सिस्टम
+
+
+
+ Light
+ हल्का
+
+
+
+ Dark
+ गहरा
+
+
+
+ SelectThemeDrawer
+
+
+ Color theme
+ रंग थीम
+
+
+
+ System
+ सिस्टम
+
+
+
+ Light
+ हल्का
+
+
+
+ Dark
+ गहरा
+
PageSettingsBackup
diff --git a/client/translations/amneziavpn_my_MM.ts b/client/translations/amneziavpn_my_MM.ts
index ad275d7365..9aeb93b483 100644
--- a/client/translations/amneziavpn_my_MM.ts
+++ b/client/translations/amneziavpn_my_MM.ts
@@ -222,6 +222,42 @@
အဆက်အသွယ်ဖြတ်နေပါသည်...
+
+ ConnectionUiController
+
+
+ Connecting...
+ ချိတ်ဆက်နေပါပြီ...
+
+
+
+ Connected
+ ချိတ်ဆက်ပြီးသွားပါပြီ
+
+
+
+ Reconnecting...
+ ပြန်လည်ချိတ်ဆက်နေပါသည်...
+
+
+
+
+
+
+ Connect
+ ချိတ်ဆက်မည်
+
+
+
+ Disconnecting...
+ အဆက်အသွယ်ဖြတ်နေပါသည်...
+
+
+
+ Preparing...
+ ပြင်ဆင်နေသည်...
+
+
ConnectionTypeSelectionDrawer
@@ -2103,6 +2139,49 @@ Already installed containers were found on the server. All installed containers
Cannot reset settings during active connection
ချိတ်ဆက်မှုရှိနေချိန်အတွင်း ဆက်တင်များကို မူရင်းအတိုင်း ပြန်လည်သတ်မှတ်၍မရပါ
+
+
+ Color theme
+ အရောင်အပြင်အဆင်
+
+
+
+ System
+ စနစ်
+
+
+
+ Light
+ အလင်း
+
+
+
+ Dark
+ အမှောင်
+
+
+
+ SelectThemeDrawer
+
+
+ Color theme
+ အရောင်အပြင်အဆင်
+
+
+
+ System
+ စနစ်
+
+
+
+ Light
+ အလင်း
+
+
+
+ Dark
+ အမှောင်
+
PageSettingsBackup
diff --git a/client/translations/amneziavpn_ru_RU.ts b/client/translations/amneziavpn_ru_RU.ts
index b1f95a688c..4022a26d86 100644
--- a/client/translations/amneziavpn_ru_RU.ts
+++ b/client/translations/amneziavpn_ru_RU.ts
@@ -307,6 +307,42 @@
Отключение...
+
+ ConnectionUiController
+
+
+ Connecting...
+ Подключение...
+
+
+
+ Connected
+ Подключено
+
+
+
+ Reconnecting...
+ Переподключение...
+
+
+
+
+
+
+ Connect
+ Подключиться
+
+
+
+ Disconnecting...
+ Отключение...
+
+
+
+ Preparing...
+ Подготовка...
+
+
ConnectionTypeSelectionDrawer
@@ -2393,6 +2429,49 @@ Thank you for staying with us!
Cannot reset settings during active connection
Невозможно сбросить настройки во время активного соединения
+
+
+ Color theme
+ Цветовая тема
+
+
+
+ System
+ Системная
+
+
+
+ Light
+ Светлая
+
+
+
+ Dark
+ Тёмная
+
+
+
+ SelectThemeDrawer
+
+
+ Color theme
+ Цветовая тема
+
+
+
+ System
+ Системная
+
+
+
+ Light
+ Светлая
+
+
+
+ Dark
+ Тёмная
+
PageSettingsBackup
diff --git a/client/translations/amneziavpn_uk_UA.ts b/client/translations/amneziavpn_uk_UA.ts
index e0fabc6b93..3220ca21e2 100644
--- a/client/translations/amneziavpn_uk_UA.ts
+++ b/client/translations/amneziavpn_uk_UA.ts
@@ -245,6 +245,42 @@
Відключаємось...
+
+ ConnectionUiController
+
+
+ Connecting...
+ Підключення...
+
+
+
+ Connected
+ Підключено
+
+
+
+ Reconnecting...
+ Перепідключення...
+
+
+
+
+
+
+ Connect
+ Підключитись
+
+
+
+ Disconnecting...
+ Відключаємось...
+
+
+
+ Preparing...
+ Підготовка...
+
+
ConnectionTypeSelectionDrawer
@@ -2268,6 +2304,49 @@ Already installed containers were found on the server. All installed containers
Cannot reset settings during active connection
Неможливо скинути налаштування під час активного підключення
+
+
+ Color theme
+ Кольорова тема
+
+
+
+ System
+ Системна
+
+
+
+ Light
+ Світла
+
+
+
+ Dark
+ Темна
+
+
+
+ SelectThemeDrawer
+
+
+ Color theme
+ Кольорова тема
+
+
+
+ System
+ Системна
+
+
+
+ Light
+ Світла
+
+
+
+ Dark
+ Темна
+
PageSettingsBackup
diff --git a/client/translations/amneziavpn_ur_PK.ts b/client/translations/amneziavpn_ur_PK.ts
index f0d63b4bc2..4f7eecc64f 100644
--- a/client/translations/amneziavpn_ur_PK.ts
+++ b/client/translations/amneziavpn_ur_PK.ts
@@ -205,6 +205,42 @@
دوبارہ ترتیب تاذہ کامیاب
+
+ ConnectionUiController
+
+
+ Connecting...
+ جوڑاجارھاھے....
+
+
+
+ Connected
+ جوڑاجارھاھے
+
+
+
+ Reconnecting...
+ دوبارہ جوڑنےکی کوشش...
+
+
+
+
+
+
+ Connect
+ جوڑنا
+
+
+
+ Disconnecting...
+ منقطع کرنا...
+
+
+
+ Preparing...
+ تیاری کیا جا رہا ہے...
+
+
ConnectionTypeSelectionDrawer
@@ -2087,6 +2123,49 @@ Already installed containers were found on the server. All installed containers
Cannot reset settings during active connection
چالو کنکشن کے دوران ترتیبات کو دوبارہ ترتیب نہیں دی جا سکتی
+
+
+ Color theme
+ رنگ تھیم
+
+
+
+ System
+ سسٹم
+
+
+
+ Light
+ روشن
+
+
+
+ Dark
+ تاریک
+
+
+
+ SelectThemeDrawer
+
+
+ Color theme
+ رنگ تھیم
+
+
+
+ System
+ سسٹم
+
+
+
+ Light
+ روشن
+
+
+
+ Dark
+ تاریک
+
PageSettingsBackup
diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts
index 16ddc2a1a6..7858622019 100644
--- a/client/translations/amneziavpn_zh_CN.ts
+++ b/client/translations/amneziavpn_zh_CN.ts
@@ -331,6 +331,42 @@
当前平台不支持所选协议
+
+ ConnectionUiController
+
+
+ Connecting...
+ 连接中
+
+
+
+ Connected
+ 已连接
+
+
+
+ Reconnecting...
+ 重连中
+
+
+
+
+
+
+ Connect
+ 连接
+
+
+
+ Disconnecting...
+ 断开中
+
+
+
+ Preparing...
+
+
+
ConnectionTypeSelectionDrawer
@@ -2542,6 +2578,49 @@ And if you don't like the app, all the more support it - the donation will
Cannot reset settings during active connection
+
+
+ Color theme
+ 颜色主题
+
+
+
+ System
+ 系统
+
+
+
+ Light
+ 浅色
+
+
+
+ Dark
+ 深色
+
+
+
+ SelectThemeDrawer
+
+
+ Color theme
+ 颜色主题
+
+
+
+ System
+ 系统
+
+
+
+ Light
+ 浅色
+
+
+
+ Dark
+ 深色
+
PageSettingsBackup
diff --git a/client/ui/controllers/appearanceController.cpp b/client/ui/controllers/appearanceController.cpp
new file mode 100644
index 0000000000..6dabcbfe9a
--- /dev/null
+++ b/client/ui/controllers/appearanceController.cpp
@@ -0,0 +1,73 @@
+#include "appearanceController.h"
+
+#include
+#include
+
+namespace
+{
+ int clampMode(int mode)
+ {
+ if (mode < static_cast(AppearanceController::ThemeMode::System)
+ || mode > static_cast(AppearanceController::ThemeMode::Dark)) {
+ return static_cast(AppearanceController::ThemeMode::System);
+ }
+ return mode;
+ }
+}
+
+AppearanceController::AppearanceController(SettingsController *settingsController, QObject *parent)
+ : QObject(parent),
+ m_settingsController(settingsController),
+ m_themeMode(static_cast(clampMode(settingsController->getThemeMode()))),
+ m_isDark(resolveIsDark())
+{
+ connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this,
+ [this](Qt::ColorScheme) { updateResolvedTheme(); });
+}
+
+int AppearanceController::getThemeMode() const
+{
+ return static_cast(m_themeMode);
+}
+
+void AppearanceController::setThemeMode(int mode)
+{
+ const auto newMode = static_cast(clampMode(mode));
+ if (newMode == m_themeMode) {
+ return;
+ }
+
+ m_themeMode = newMode;
+ m_settingsController->setThemeMode(static_cast(newMode));
+ emit themeModeChanged();
+
+ updateResolvedTheme();
+}
+
+bool AppearanceController::isDarkMode() const
+{
+ return m_isDark;
+}
+
+bool AppearanceController::resolveIsDark() const
+{
+ switch (m_themeMode) {
+ case ThemeMode::Light:
+ return false;
+ case ThemeMode::Dark:
+ return true;
+ default:
+ return QGuiApplication::styleHints()->colorScheme() != Qt::ColorScheme::Light;
+ }
+}
+
+void AppearanceController::updateResolvedTheme()
+{
+ const bool isDark = resolveIsDark();
+ if (isDark == m_isDark) {
+ return;
+ }
+
+ m_isDark = isDark;
+ emit isDarkModeChanged();
+}
diff --git a/client/ui/controllers/appearanceController.h b/client/ui/controllers/appearanceController.h
new file mode 100644
index 0000000000..85fbd6b62c
--- /dev/null
+++ b/client/ui/controllers/appearanceController.h
@@ -0,0 +1,37 @@
+#ifndef APPEARANCECONTROLLER_H
+#define APPEARANCECONTROLLER_H
+
+#include
+
+#include "core/controllers/settingsController.h"
+
+class AppearanceController : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int themeMode READ getThemeMode WRITE setThemeMode NOTIFY themeModeChanged)
+ Q_PROPERTY(bool isDarkMode READ isDarkMode NOTIFY isDarkModeChanged)
+
+public:
+ enum class ThemeMode { System = 0, Light = 1, Dark = 2 };
+
+ explicit AppearanceController(SettingsController *settingsController, QObject *parent = nullptr);
+
+ int getThemeMode() const;
+ void setThemeMode(int mode);
+ bool isDarkMode() const;
+
+signals:
+ void themeModeChanged();
+ void isDarkModeChanged();
+
+private:
+ bool resolveIsDark() const;
+ void updateResolvedTheme();
+
+ SettingsController *m_settingsController;
+ ThemeMode m_themeMode;
+ bool m_isDark;
+};
+
+#endif // APPEARANCECONTROLLER_H
diff --git a/client/ui/qml/Components/AddSitePanel.qml b/client/ui/qml/Components/AddSitePanel.qml
index 18fdfa572c..bcf4979e4e 100644
--- a/client/ui/qml/Components/AddSitePanel.qml
+++ b/client/ui/qml/Components/AddSitePanel.qml
@@ -22,7 +22,7 @@ Item {
Rectangle {
id: background
anchors.fill: parent
- color: "#0E0F12"
+ color: AmneziaStyle.color.midnightBlack
opacity: 0.85
z: -1
}
diff --git a/client/ui/qml/Components/ChangelogDrawer.qml b/client/ui/qml/Components/ChangelogDrawer.qml
index 1bb767be41..adcb2d20de 100644
--- a/client/ui/qml/Components/ChangelogDrawer.qml
+++ b/client/ui/qml/Components/ChangelogDrawer.qml
@@ -2,6 +2,8 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
+import Style 1.0
+
import "../Controls2"
import "../Controls2/TextTypes"
@@ -86,11 +88,11 @@ DrawerType2 {
anchors.rightMargin: 16
anchors.leftMargin: 16
- defaultColor: "transparent"
- hoveredColor: Qt.rgba(1, 1, 1, 0.08)
- pressedColor: Qt.rgba(1, 1, 1, 0.12)
- disabledColor: "#878B91"
- textColor: "#D7D8DB"
+ defaultColor: AmneziaStyle.color.transparent
+ hoveredColor: AmneziaStyle.color.translucentWhite
+ pressedColor: AmneziaStyle.color.sheerWhite
+ disabledColor: AmneziaStyle.color.mutedGray
+ textColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: qsTr("Skip")
diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml
index f66ba77bbd..9fe3c2ce87 100644
--- a/client/ui/qml/Components/ConnectButton.qml
+++ b/client/ui/qml/Components/ConnectButton.qml
@@ -77,7 +77,8 @@ Button {
verticalOffset: 0
radius: 10
samples: 25
- color: root.buttonActiveFocus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.goldenApricot
+ color: AmneziaStyle.isDark ? (root.buttonActiveFocus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.goldenApricot)
+ : AmneziaStyle.color.transparent
source: backgroundCircle
}
diff --git a/client/ui/qml/Components/SelectThemeDrawer.qml b/client/ui/qml/Components/SelectThemeDrawer.qml
new file mode 100644
index 0000000000..87a4c1fba2
--- /dev/null
+++ b/client/ui/qml/Components/SelectThemeDrawer.qml
@@ -0,0 +1,174 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import Style 1.0
+
+import "../Controls2"
+import "../Controls2/TextTypes"
+
+DrawerType2 {
+ id: root
+
+ expandedStateContent: Item {
+ id: container
+
+ implicitHeight: root.height * 0.9
+
+ Component.onCompleted: {
+ root.expandedHeight = container.implicitHeight
+ }
+
+ ColumnLayout {
+ id: backButtonLayout
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.topMargin: 16
+
+ BackButtonType {
+ id: backButton
+
+ Layout.fillWidth: true
+
+ backButtonImage: "qrc:/images/controls/arrow-left.svg"
+ backButtonFunction: function() { root.closeTriggered() }
+ }
+
+ Header2Type {
+ id: header
+
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+ Layout.rightMargin: 16
+ Layout.leftMargin: 16
+
+ headerText: qsTr("Color theme")
+ }
+ }
+
+ ListViewType {
+ id: listView
+
+ anchors.top: backButtonLayout.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+
+ property int selectedIndex: AppearanceController.themeMode
+
+ model: ListModel {
+ ListElement { name: qsTr("System"); mode: 0 }
+ ListElement { name: qsTr("Light"); mode: 1 }
+ ListElement { name: qsTr("Dark"); mode: 2 }
+ }
+
+ ButtonGroup {
+ id: buttonGroup
+ }
+
+ delegate: Item {
+ implicitWidth: root.width
+ implicitHeight: delegateContent.implicitHeight
+
+ ColumnLayout {
+ id: delegateContent
+
+ anchors.fill: parent
+
+ RadioButton {
+ id: radioButton
+
+ implicitWidth: parent.width
+ implicitHeight: radioButtonContent.implicitHeight
+
+ hoverEnabled: true
+
+ property bool isFocusable: true
+
+ Keys.onTabPressed: {
+ FocusController.nextKeyTabItem()
+ }
+
+ Keys.onBacktabPressed: {
+ FocusController.previousKeyTabItem()
+ }
+
+ Keys.onUpPressed: {
+ FocusController.nextKeyUpItem()
+ }
+
+ Keys.onDownPressed: {
+ FocusController.nextKeyDownItem()
+ }
+
+ Keys.onLeftPressed: {
+ FocusController.nextKeyLeftItem()
+ }
+
+ Keys.onRightPressed: {
+ FocusController.nextKeyRightItem()
+ }
+
+ indicator: Rectangle {
+ width: parent.width - 1
+ height: parent.height
+ color: radioButton.hovered ? AmneziaStyle.color.slateGray : AmneziaStyle.color.onyxBlack
+ border.color: radioButton.focus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.transparent
+ border.width: radioButton.focus ? 1 : 0
+
+ Behavior on color {
+ PropertyAnimation { duration: 200 }
+ }
+ Behavior on border.color {
+ PropertyAnimation { duration: 200 }
+ }
+ }
+
+ RowLayout {
+ id: radioButtonContent
+ anchors.fill: parent
+
+ anchors.rightMargin: 16
+ anchors.leftMargin: 16
+
+ spacing: 0
+
+ z: 1
+
+ ParagraphTextType {
+ Layout.fillWidth: true
+ Layout.topMargin: 20
+ Layout.bottomMargin: 20
+
+ text: name
+ }
+
+ Image {
+ source: "qrc:/images/controls/check.svg"
+ visible: radioButton.checked
+
+ width: 24
+ height: 24
+
+ Layout.rightMargin: 8
+ }
+ }
+
+ ButtonGroup.group: buttonGroup
+ checked: listView.selectedIndex === index
+
+ onClicked: {
+ AppearanceController.themeMode = mode
+ root.closeTriggered()
+ }
+ }
+ }
+
+ Keys.onEnterPressed: radioButton.clicked()
+ Keys.onReturnPressed: radioButton.clicked()
+ }
+ }
+ }
+}
diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml
index 769c3e046c..df16f4b493 100644
--- a/client/ui/qml/Controls2/BasicButtonType.qml
+++ b/client/ui/qml/Controls2/BasicButtonType.qml
@@ -10,12 +10,12 @@ import "TextTypes"
Button {
id: root
- property string hoveredColor: AmneziaStyle.color.lightGray
- property string defaultColor: AmneziaStyle.color.paleGray
- property string disabledColor: AmneziaStyle.color.charcoalGray
- property string pressedColor: AmneziaStyle.color.mutedGray
+ property string hoveredColor: AmneziaStyle.color.primaryButtonHovered
+ property string defaultColor: AmneziaStyle.color.primaryButton
+ property string disabledColor: AmneziaStyle.color.primaryButtonDisabled
+ property string pressedColor: AmneziaStyle.color.primaryButtonPressed
- property string textColor: AmneziaStyle.color.midnightBlack
+ property string textColor: AmneziaStyle.color.primaryButtonText
property string borderColor: AmneziaStyle.color.paleGray
property string borderFocusedColor: AmneziaStyle.color.paleGray
diff --git a/client/ui/qml/Controls2/CardWithIconsType.qml b/client/ui/qml/Controls2/CardWithIconsType.qml
index 95f462302f..23787d06a9 100644
--- a/client/ui/qml/Controls2/CardWithIconsType.qml
+++ b/client/ui/qml/Controls2/CardWithIconsType.qml
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
+import Qt5Compat.GraphicalEffects
import Style 1.0
@@ -27,6 +28,7 @@ Button {
property string rightImageColor: AmneziaStyle.color.paleGray
property string leftImageSource
+ property string leftImageColor: AmneziaStyle.color.mutedGray
property alias focusItem: rightImage
@@ -114,6 +116,11 @@ Button {
Layout.topMargin: 24
Layout.bottomMargin: 24
Layout.leftMargin: 24
+
+ layer.enabled: true
+ layer.effect: ColorOverlay {
+ color: root.leftImageColor
+ }
}
ColumnLayout {
diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml
index 540028159b..bdbf0c5918 100644
--- a/client/ui/qml/Controls2/DropDownType.qml
+++ b/client/ui/qml/Controls2/DropDownType.qml
@@ -40,6 +40,7 @@ Item {
property int rootButtonTextLeftMargins: 16
property int rootButtonTextTopMargin: 16
property int rootButtonTextBottomMargin: 16
+ property int rootButtonImageRightMargin: 16
property real drawerHeight: 0.9
property Item drawerParent
@@ -188,7 +189,7 @@ Item {
}
ImageButtonType {
- Layout.rightMargin: 16
+ Layout.rightMargin: root.rootButtonImageRightMargin
implicitWidth: 40
implicitHeight: 40
diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml
index b52e0fc581..94bd621a25 100644
--- a/client/ui/qml/Controls2/PopupType.qml
+++ b/client/ui/qml/Controls2/PopupType.qml
@@ -39,7 +39,7 @@ Popup {
background: Rectangle {
anchors.fill: parent
- color: "white"
+ color: AmneziaStyle.color.notificationBackground
radius: 4
}
@@ -90,7 +90,7 @@ Popup {
implicitHeight: 32
- defaultColor: "white"
+ defaultColor: AmneziaStyle.color.notificationBackground
hoveredColor: AmneziaStyle.color.lightGray
pressedColor: AmneziaStyle.color.lightGray
disabledColor: AmneziaStyle.color.charcoalGray
diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml
index b2500b5389..5f1723dcc4 100644
--- a/client/ui/qml/Controls2/SwitcherType.qml
+++ b/client/ui/qml/Controls2/SwitcherType.qml
@@ -16,20 +16,20 @@ Switch {
property string textColor: AmneziaStyle.color.paleGray
property string textDisabledColor: AmneziaStyle.color.mutedGray
- property string checkedIndicatorColor: AmneziaStyle.color.richBrown
+ property string checkedIndicatorColor: AmneziaStyle.color.switchCheckedTrack
property string defaultIndicatorColor: AmneziaStyle.color.transparent
- property string checkedDisabledIndicatorColor: AmneziaStyle.color.deepBrown
+ property string checkedDisabledIndicatorColor: AmneziaStyle.color.switchCheckedDisabledTrack
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderFocusedWidth: 1
- property string checkedIndicatorBorderColor: AmneziaStyle.color.richBrown
+ property string checkedIndicatorBorderColor: AmneziaStyle.color.switchCheckedTrackBorder
property string defaultIndicatorBorderColor: AmneziaStyle.color.charcoalGray
- property string checkedDisabledIndicatorBorderColor: AmneziaStyle.color.deepBrown
+ property string checkedDisabledIndicatorBorderColor: AmneziaStyle.color.switchCheckedDisabledTrackBorder
- property string checkedInnerCircleColor: AmneziaStyle.color.goldenApricot
+ property string checkedInnerCircleColor: AmneziaStyle.color.switchCheckedKnob
property string defaultInnerCircleColor: AmneziaStyle.color.paleGray
- property string checkedDisabledInnerCircleColor: AmneziaStyle.color.mutedBrown
+ property string checkedDisabledInnerCircleColor: AmneziaStyle.color.switchCheckedDisabledKnob
property string defaultDisabledInnerCircleColor: AmneziaStyle.color.charcoalGray
property string hoveredIndicatorBackgroundColor: AmneziaStyle.color.translucentWhite
diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml
index 7ad6afc84b..8fbd8e8fa4 100644
--- a/client/ui/qml/Controls2/VerticalRadioButton.qml
+++ b/client/ui/qml/Controls2/VerticalRadioButton.qml
@@ -1,7 +1,6 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
-import Qt5Compat.GraphicalEffects
import Style 1.0
@@ -35,10 +34,8 @@ RadioButton {
property bool isFocusable: true
- property string radioButtonInnerCirclePressedSource: "qrc:/images/controls/radio-button-inner-circle-pressed.png"
- property string radioButtonInnerCircleSource: "qrc:/images/controls/radio-button-inner-circle.png"
- property string radioButtonPressedSource: "qrc:/images/controls/radio-button-pressed.svg"
- property string radioButtonDefaultSource: "qrc:/images/controls/radio-button.svg"
+ property string selectedRingColor: AmneziaStyle.color.goldenApricot
+ property string defaultRingColor: AmneziaStyle.color.mutedGray
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
@@ -100,17 +97,8 @@ RadioButton {
}
Image {
- source: {
- if (showImage) {
- return imageSource
- } else if (root.pressed) {
- return root.radioButtonInnerCirclePressedSource
- } else if (root.checked) {
- return root.radioButtonInnerCircleSource
- }
-
- return ""
- }
+ visible: root.showImage
+ source: root.showImage ? root.imageSource : ""
opacity: root.enabled ? 1.0 : 0.3
anchors.centerIn: parent
@@ -119,22 +107,34 @@ RadioButton {
height: 24
}
- Image {
- source: {
- if (showImage) {
- return ""
- } else if (root.pressed || root.checked) {
- return root.radioButtonPressedSource
- } else {
- return root.radioButtonDefaultSource
- }
- }
+ Rectangle {
+ visible: !root.showImage
- opacity: root.enabled ? 1.0 : 0.3
anchors.centerIn: parent
width: 24
height: 24
+ radius: width / 2
+
+ color: AmneziaStyle.color.transparent
+ border.width: 1.5
+ border.color: root.checked ? root.selectedRingColor : root.defaultRingColor
+ opacity: root.enabled ? 1.0 : 0.3
+
+ Behavior on border.color {
+ PropertyAnimation { duration: 200 }
+ }
+
+ Rectangle {
+ anchors.centerIn: parent
+
+ width: 12
+ height: 12
+ radius: width / 2
+
+ visible: root.checked
+ color: root.selectedRingColor
+ }
}
}
@@ -162,10 +162,12 @@ RadioButton {
color: {
if (root.enabled) {
return root.checked ? selectedTextColor : textColor
- } else {
- return root.checked ? selectedTextDisabledColor : textDisabledColor
}
+ // When checked but disabled, match the radio indicator
+ // (selectedRingColor at 0.3 opacity below).
+ return root.checked ? root.selectedRingColor : textDisabledColor
}
+ opacity: (!root.enabled && root.checked) ? 0.3 : 1.0
Layout.fillWidth: true
diff --git a/client/ui/qml/Modules/Style/AmneziaStyle.qml b/client/ui/qml/Modules/Style/AmneziaStyle.qml
index 6c81dc9afc..5d0cf8c0c8 100644
--- a/client/ui/qml/Modules/Style/AmneziaStyle.qml
+++ b/client/ui/qml/Modules/Style/AmneziaStyle.qml
@@ -3,7 +3,23 @@ pragma Singleton
import QtQuick
QtObject {
- property QtObject color: QtObject {
+ id: root
+
+ // Whether the dark color set is currently active. Driven from C++ via
+ // AppearanceController (see main2.qml); defaults to dark to match the
+ // historical look on first load and for any context without the binding.
+ property bool isDark: true
+
+ // Active palette. All call sites read AmneziaStyle.color.; switching
+ // this object re-evaluates every binding, so the whole UI re-themes live.
+ readonly property QtObject color: isDark ? darkColor : lightColor
+
+ // Color names are historical/literal (e.g. "midnightBlack"). To avoid
+ // touching the hundreds of existing call sites, the light set keeps the same
+ // names but assigns each its light-mode role equivalent (so "midnightBlack",
+ // the app background, becomes white in light mode, and so on).
+
+ readonly property QtObject darkColor: QtObject {
readonly property color transparent: 'transparent'
readonly property color paleGray: '#D7D8DB'
readonly property color lightGray: '#C1C2C5'
@@ -23,6 +39,28 @@ QtObject {
readonly property color darkCharcoal: '#261E1A'
readonly property color pearlGray: '#EAEAEC'
+ // Checked switch (SwitcherType): brown track with an apricot knob in dark mode.
+ // (Light mode flips to an apricot track with a white knob — see lightColor.)
+ readonly property color switchCheckedTrack: richBrown
+ readonly property color switchCheckedTrackBorder: richBrown
+ readonly property color switchCheckedKnob: goldenApricot
+
+ // Checked + disabled switch: deep-brown track, muted-brown knob in dark mode.
+ readonly property color switchCheckedDisabledTrack: deepBrown
+ readonly property color switchCheckedDisabledTrackBorder: deepBrown
+ readonly property color switchCheckedDisabledKnob: mutedBrown
+
+ // Notification/toast surface (PopupType). White here so the dark midnightBlack
+ // text reads; light mode flips to a dark surface (see lightColor) so the popup
+ // stays legible and stands out against the light background.
+ readonly property color notificationBackground: '#FFFFFF'
+
+ readonly property color primaryButton: paleGray
+ readonly property color primaryButtonHovered: lightGray
+ readonly property color primaryButtonPressed: mutedGray
+ readonly property color primaryButtonDisabled: charcoalGray
+ readonly property color primaryButtonText: midnightBlack
+
readonly property color sheerWhite: Qt.rgba(1, 1, 1, 0.12)
readonly property color translucentWhite: Qt.rgba(1, 1, 1, 0.08)
readonly property color barelyTranslucentWhite: Qt.rgba(1, 1, 1, 0.05)
@@ -36,4 +74,69 @@ QtObject {
readonly property string goldenApricotString: '#FBB26A'
}
+
+ readonly property QtObject lightColor: QtObject {
+ readonly property color transparent: 'transparent'
+ // Neutrals: in dark mode these run light-foreground -> deep-background.
+ // In light mode they invert to dark-foreground -> light-background.
+ readonly property color paleGray: '#16171A' // primary text/icon
+ readonly property color lightGray: '#2C2D30' // bright secondary text
+ readonly property color mutedGray: '#6D7177' // muted secondary text
+ readonly property color charcoalGray: '#C1C2C5' // borders / disabled
+ readonly property color slateGray: '#E3E4E7' // hovered/elevated surface
+ readonly property color onyxBlack: '#F2F3F5' // card / surface background
+ readonly property color midnightBlack: '#FFFFFF' // app background
+ readonly property color goldenApricot: goldenApricotString
+ readonly property color benefitsPanelBackground: '#F2F3F5'
+ // Brand accents are kept identical across themes.
+ readonly property color softViolet: '#A87BE2'
+ readonly property color burntOrange: '#A85809'
+ readonly property color mutedBrown: '#84603D'
+ readonly property color richBrown: '#633303'
+ readonly property color deepBrown: '#402102'
+ readonly property color vibrantRed: '#EB5757'
+ readonly property color darkCharcoal: '#E3E4E7' // subtle "in progress" ring
+ readonly property color pearlGray: '#2C2D30' // near-foreground text
+
+ // "On" switch reads as a solid apricot track (the knob's brand color) with a
+ // white knob — the conventional light-mode toggle look.
+ readonly property color switchCheckedTrack: goldenApricot
+ readonly property color switchCheckedTrackBorder: goldenApricot
+ readonly property color switchCheckedKnob: '#FFFFFF'
+
+ // Disabled "on" pill: a muted/faded apricot track (clearly less vivid than the
+ // enabled #E38E41) with a white knob, so it reads as on-but-inactive.
+ readonly property color switchCheckedDisabledTrack: '#E2B58C'
+ readonly property color switchCheckedDisabledTrackBorder: '#E2B58C'
+ readonly property color switchCheckedDisabledKnob: '#FFFFFF'
+
+ // Dark toast on the light UI so the white midnightBlack text reads (the old
+ // hardcoded-white popup rendered white-on-white, hiding the message).
+ readonly property color notificationBackground: '#3A3B40'
+
+ // Primary filled button: kept the softer apricot (the deeper #E38E41 is reserved
+ // for highlights — outlines/text/icons — where contrast matters more).
+ readonly property color primaryButton: '#FBB26A'
+ readonly property color primaryButtonHovered: '#FFC078'
+ readonly property color primaryButtonPressed: '#F2A04E'
+ readonly property color primaryButtonDisabled: slateGray
+ readonly property color primaryButtonText: '#1C1D21'
+
+ // White overlays (hover/press tints) become black overlays on light.
+ readonly property color sheerWhite: Qt.rgba(0, 0, 0, 0.10)
+ readonly property color translucentWhite: Qt.rgba(0, 0, 0, 0.06)
+ readonly property color barelyTranslucentWhite: Qt.rgba(0, 0, 0, 0.04)
+ // Modal scrim stays a dark dim in both themes, just gentler on light.
+ readonly property color translucentMidnightBlack: Qt.rgba(14/255, 14/255, 17/255, 0.4)
+ readonly property color softGoldenApricot: Qt.rgba(251/255, 178/255, 106/255, 0.3)
+ // Translucent light-gray foregrounds invert to translucent dark.
+ readonly property color mistyGray: Qt.rgba(28/255, 29/255, 33/255, 0.8)
+ readonly property color cloudyGray: Qt.rgba(28/255, 29/255, 33/255, 0.65)
+ readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26)
+ readonly property color translucentSlateGray: Qt.rgba(44/255, 45/255, 48/255, 0.08)
+ readonly property color translucentOnyxBlack: Qt.rgba(28/255, 29/255, 33/255, 0.06)
+
+ // Deeper apricot than dark mode's #FBB26A — the pale tone is too low-contrast on white.
+ readonly property string goldenApricotString: '#E38E41'
+ }
}
diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml
index 7298aea9ae..c59bbcd90d 100644
--- a/client/ui/qml/Pages2/PageHome.qml
+++ b/client/ui/qml/Pages2/PageHome.qml
@@ -388,13 +388,20 @@ PageType {
Component.onCompleted: root.containersDropDownRef = containersDropDown
rootButtonImageColor: AmneziaStyle.color.midnightBlack
- rootButtonBackgroundColor: AmneziaStyle.color.paleGray
+ // Inverted "prominent" chip: a light pill in dark mode; in light mode
+ // a softened charcoal (not the harsh near-black paleGray) keeps it
+ // prominent without clashing with the soft light surface.
+ rootButtonBackgroundColor: AmneziaStyle.isDark ? AmneziaStyle.color.paleGray : "#3A3B40"
rootButtonBackgroundHoveredColor: AmneziaStyle.color.mistyGray
rootButtonBackgroundPressedColor: AmneziaStyle.color.cloudyGray
rootButtonHoveredBorderColor: AmneziaStyle.color.transparent
rootButtonDefaultBorderColor: AmneziaStyle.color.transparent
rootButtonTextTopMargin: 8
rootButtonTextBottomMargin: 8
+ // Short content-hugging pill: trim the chevron's right margin so the
+ // caret sits ~symmetric with the 16px text left margin (the 40px icon
+ // touch-target already adds ~8px of centering padding).
+ rootButtonImageRightMargin: 8
enabled: drawer.isOpened
diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml
index 16593d070a..3393fee061 100644
--- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml
+++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml
@@ -183,11 +183,11 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
- readonly property string windowsFirstLink: "WinFsp"
- readonly property string windowsSecondLink: "SSHFS-Win"
+ readonly property string windowsFirstLink: "WinFsp"
+ readonly property string windowsSecondLink: "SSHFS-Win"
- readonly property string macosFirstLink: "macFUSE"
- readonly property string macosSecondLink: "SSHFS"
+ readonly property string macosFirstLink: "macFUSE"
+ readonly property string macosSecondLink: "SSHFS"
onLinkActivated: function(link) {
Qt.openUrlExternally(link)
diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml
index 0077e55c1f..c02b40becb 100644
--- a/client/ui/qml/Pages2/PageSettingsApplication.qml
+++ b/client/ui/qml/Pages2/PageSettingsApplication.qml
@@ -213,6 +213,22 @@ PageType {
DividerType {}
+ LabelWithButtonType {
+ id: labelWithButtonTheme
+
+ Layout.fillWidth: true
+
+ text: qsTr("Color theme")
+ descriptionText: [qsTr("System"), qsTr("Light"), qsTr("Dark")][AppearanceController.themeMode]
+ rightImageSource: "qrc:/images/controls/chevron-right.svg"
+
+ clickedFunction: function() {
+ selectThemeDrawer.openTriggered()
+ }
+ }
+
+ DividerType {}
+
LabelWithButtonType {
id: labelWithButtonLogging
@@ -270,4 +286,11 @@ PageType {
width: root.width
height: root.height
}
+
+ SelectThemeDrawer {
+ id: selectThemeDrawer
+
+ width: root.width
+ height: root.height
+ }
}
diff --git a/client/ui/qml/Pages2/PageSettingsNewsDetail.qml b/client/ui/qml/Pages2/PageSettingsNewsDetail.qml
index b304ab5a28..2d25b60497 100644
--- a/client/ui/qml/Pages2/PageSettingsNewsDetail.qml
+++ b/client/ui/qml/Pages2/PageSettingsNewsDetail.qml
@@ -103,11 +103,11 @@ PageType {
Layout.topMargin: 8
Layout.bottomMargin: 16
visible: root.isUpdateItem
- defaultColor: "transparent"
- hoveredColor: Qt.rgba(1, 1, 1, 0.08)
- pressedColor: Qt.rgba(1, 1, 1, 0.12)
- disabledColor: "#878B91"
- textColor: "#D7D8DB"
+ defaultColor: AmneziaStyle.color.transparent
+ hoveredColor: AmneziaStyle.color.translucentWhite
+ pressedColor: AmneziaStyle.color.sheerWhite
+ disabledColor: AmneziaStyle.color.mutedGray
+ textColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: qsTr("Skip")
diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml
index 18e8f87e8b..5581661ea0 100644
--- a/client/ui/qml/main2.qml
+++ b/client/ui/qml/main2.qml
@@ -16,6 +16,12 @@ Window {
id: root
objectName: "mainWindow"
+ Binding {
+ target: AmneziaStyle
+ property: "isDark"
+ value: AppearanceController.isDarkMode
+ }
+
Connections {
target: Qt.application
function onStateChanged() {
diff --git a/client/ui/qml/qml.qrc b/client/ui/qml/qml.qrc
index b7e65a6817..cdba3671d8 100644
--- a/client/ui/qml/qml.qrc
+++ b/client/ui/qml/qml.qrc
@@ -10,6 +10,7 @@
Components/ChangelogDrawer.qml
Components/QuestionDrawer.qml
Components/SelectLanguageDrawer.qml
+ Components/SelectThemeDrawer.qml
Components/ServersListView.qml
Components/SettingsContainersListView.qml
Components/BenefitRow.qml