From 8f5c45dd84aae40dbfb3d87fd840ead6ae91cf4e Mon Sep 17 00:00:00 2001
From: jarolrod <jarolrod@tutanota.com>
Date: Thu, 20 Apr 2023 18:48:00 -0400
Subject: [PATCH] qml: introduce ThemeManager

---
 src/Makefile.qt.include              |  3 ++
 src/qml/bitcoin.cpp                  |  3 ++
 src/qml/components/ThemeSettings.qml | 33 ++++++++++++++++---
 src/qml/controls/Theme.qml           | 21 +++++++++++-
 src/qml/thememanager.cpp             | 48 ++++++++++++++++++++++++++++
 src/qml/thememanager.h               | 41 ++++++++++++++++++++++++
 6 files changed, 144 insertions(+), 5 deletions(-)
 create mode 100644 src/qml/thememanager.cpp
 create mode 100644 src/qml/thememanager.h

diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include
index ba032ee58f..2c1504bbda 100644
--- a/src/Makefile.qt.include
+++ b/src/Makefile.qt.include
@@ -43,6 +43,7 @@ QT_MOC_CPP = \
   qml/models/moc_options_model.cpp \
   qml/models/moc_peerlistsortproxy.cpp \
   qml/moc_appmode.cpp \
+  qml/moc_thememanager.cpp \
   qt/moc_addressbookpage.cpp \
   qt/moc_addresstablemodel.cpp \
   qt/moc_askpassphrasedialog.cpp \
@@ -124,6 +125,7 @@ BITCOIN_QT_H = \
   qml/appmode.h \
   qml/bitcoin.h \
   qml/imageprovider.h \
+  qml/thememanager.h \
   qml/util.h \
   qt/addressbookpage.h \
   qt/addresstablemodel.h \
@@ -309,6 +311,7 @@ BITCOIN_QML_BASE_CPP = \
   qml/models/options_model.cpp \
   qml/models/peerlistsortproxy.cpp \
   qml/imageprovider.cpp \
+  qml/thememanager.cpp \
   qml/util.cpp
 
 QML_RES_FONTS = \
diff --git a/src/qml/bitcoin.cpp b/src/qml/bitcoin.cpp
index 10e8a959d0..68460ef800 100644
--- a/src/qml/bitcoin.cpp
+++ b/src/qml/bitcoin.cpp
@@ -26,6 +26,7 @@
 #include <qml/models/options_model.h>
 #include <qml/models/peerlistsortproxy.h>
 #include <qml/imageprovider.h>
+#include <qml/thememanager.h>
 #include <qml/util.h>
 #include <qt/guiconstants.h>
 #include <qt/guiutil.h>
@@ -243,6 +244,7 @@ int QmlGuiMain(int argc, char* argv[])
     QObject::connect(&init_executor, &InitExecutor::shutdownResult, qGuiApp, &QGuiApplication::quit, Qt::QueuedConnection);
     // QObject::connect(&init_executor, &InitExecutor::runawayException, &node_model, &NodeModel::handleRunawayException);
 
+    ThemeManager theme_manager{};
     NetworkTrafficTower network_traffic_tower{node_model};
 #ifdef __ANDROID__
     AndroidNotifier android_notifier{node_model};
@@ -278,6 +280,7 @@ int QmlGuiMain(int argc, char* argv[])
     engine.rootContext()->setContextProperty("chainModel", &chain_model);
     engine.rootContext()->setContextProperty("peerTableModel", &peer_model);
     engine.rootContext()->setContextProperty("peerListModelProxy", &peer_model_sort_proxy);
+    engine.rootContext()->setContextProperty("themeManager", &theme_manager);
 
     OptionsQmlModel options_model{*node};
     engine.rootContext()->setContextProperty("optionsModel", &options_model);
diff --git a/src/qml/components/ThemeSettings.qml b/src/qml/components/ThemeSettings.qml
index 10e4db7f95..639f540718 100644
--- a/src/qml/components/ThemeSettings.qml
+++ b/src/qml/components/ThemeSettings.qml
@@ -21,13 +21,14 @@ ColumnLayout {
         header: qsTr("Light")
         actionItem: Icon {
             anchors.centerIn: parent
-            visible: !Theme.dark
+            visible: !Theme.manualDark && Theme.manualTheme
             source: "image://images/check"
             color: Theme.color.neutral9
             size: 24
         }
         onClicked: {
-            Theme.dark = false
+            Theme.manualTheme = true;
+            Theme.manualDark = false;
         }
     }
     Separator { Layout.fillWidth: true }
@@ -36,13 +37,37 @@ ColumnLayout {
         header: qsTr("Dark")
         actionItem: Icon {
             anchors.centerIn: parent
-            visible: Theme.dark
+            visible: Theme.manualDark && Theme.manualTheme
             source: "image://images/check"
             color: Theme.color.neutral9
             size: 24
         }
         onClicked: {
-            Theme.dark = true;
+            Theme.manualTheme = true;
+            Theme.manualDark = true;
+        }
+    }
+    Separator { Layout.fillWidth: true }
+    Setting {
+        id: systemThemeSetting
+        property bool systemThemeAvailable: Theme.systemThemeAvailable
+        Layout.fillWidth: true
+        header: qsTr("System")
+        actionItem: Icon {
+            anchors.centerIn: parent
+            visible: !Theme.manualTheme
+            source: "image://images/check"
+            size: 24
+        }
+        Component.onCompleted: {
+            if (systemThemeAvailable) {
+                systemThemeSetting.state = "FILLED"
+            } else {
+                systemThemeSetting.state = "DISABLED"
+            }
+        }
+        onClicked: {
+            Theme.manualTheme = false
         }
     }
 }
diff --git a/src/qml/controls/Theme.qml b/src/qml/controls/Theme.qml
index df62e0a4ad..d6913bceee 100644
--- a/src/qml/controls/Theme.qml
+++ b/src/qml/controls/Theme.qml
@@ -3,9 +3,14 @@ import QtQuick 2.15
 import QtQuick.Controls 2.15
 import Qt.labs.settings 1.0
 
+import org.bitcoincore.qt 1.0
+
 Control {
     id: root
-    property bool dark: true
+    property bool dark: manualTheme ? manualDark : themeManager.darkMode
+    property bool systemThemeAvailable: themeManager.systemThemeAvailable
+    property bool manualTheme: false
+    property bool manualDark: true
     property real blockclocksize: (5/12)
     readonly property ColorSet color: dark ? darkColorSet : lightColorSet
     readonly property ImageSet image: dark ? darkImageSet : lightImageSet
@@ -13,9 +18,23 @@ Control {
     Settings {
         id: settings
         property alias dark: root.dark
+        property alias manualTheme: root.manualTheme
+        property alias manualDark: root.manualDark
         property alias blockclocksize: root.blockclocksize
     }
 
+    SystemPalette {
+        id: systemColor
+
+        onBaseChanged: {
+            themeManager.systemBaseColor = systemColor.base
+        }
+    }
+
+    Component.onCompleted: {
+        themeManager.systemBaseColor = systemColor.base
+    }
+
     component ColorSet: QtObject {
         required property color white
         required property color background
diff --git a/src/qml/thememanager.cpp b/src/qml/thememanager.cpp
new file mode 100644
index 0000000000..3c6c420d8d
--- /dev/null
+++ b/src/qml/thememanager.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2023 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <qml/thememanager.h>
+
+ThemeManager::ThemeManager(QObject* parent)
+    : QObject(parent)
+{
+}
+
+void ThemeManager::setSystemBaseColor(QColor base_color)
+{
+    // Convert QColor's 8-bit RGB values to linear RGB values
+    double linearR = base_color.redF();
+    double linearG = base_color.greenF();
+    double linearB = base_color.blueF();
+
+    // Constants for the luminance formula
+    const double RED_FACTOR = 0.2126;
+    const double GREEN_FACTOR = 0.7152;
+    const double BLUE_FACTOR = 0.0722;
+
+    // Calculate luminance using the formula
+    double luminance = RED_FACTOR * linearR + GREEN_FACTOR * linearG + BLUE_FACTOR * linearB;
+
+    if (luminance <= 0.5) {
+        m_dark_mode = true;
+    } else {
+        m_dark_mode = false;
+    }
+
+    m_system_base_color = base_color;
+
+    #ifdef Q_OS_MAC
+    setSystemThemeAvailable(true);
+    #endif
+
+    Q_EMIT darkModeChanged();
+}
+
+void ThemeManager::setSystemThemeAvailable(bool available)
+{
+    if (m_system_theme_available != available) {
+        m_system_theme_available = available;
+        Q_EMIT systemThemeAvailableChanged();
+    }
+}
\ No newline at end of file
diff --git a/src/qml/thememanager.h b/src/qml/thememanager.h
new file mode 100644
index 0000000000..05d095c1ce
--- /dev/null
+++ b/src/qml/thememanager.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2023 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_QML_THEMEMANAGER_H
+#define BITCOIN_QML_THEMEMANAGER_H
+
+#include <QObject>
+#include <QColor>
+#include <QProcess>
+
+
+class ThemeManager : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(bool darkMode READ darkMode NOTIFY darkModeChanged)
+    Q_PROPERTY(QColor systemBaseColor READ systemBaseColor WRITE setSystemBaseColor)
+    Q_PROPERTY(bool systemThemeAvailable READ systemThemeAvailable WRITE setSystemThemeAvailable NOTIFY systemThemeAvailableChanged)
+
+public:
+    explicit ThemeManager(QObject* parent = nullptr);
+
+    bool darkMode() const { return m_dark_mode; };
+    QColor systemBaseColor() const { return m_system_base_color; };
+    bool systemThemeAvailable() const { return m_system_theme_available; };
+
+public Q_SLOTS:
+    void setSystemBaseColor(QColor base_color);
+    void setSystemThemeAvailable(bool available);
+
+Q_SIGNALS:
+    void darkModeChanged();
+    void systemThemeAvailableChanged();
+
+private:
+    bool m_dark_mode{ true };
+    QColor m_system_base_color;
+    bool m_system_theme_available{ false };
+};
+
+#endif // BITCOIN_QML_THEMEMANAGER_H
\ No newline at end of file