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