Fix dark mode stuff relating to unified search, fix macOS auto dark/light theme switc...
authorClaudio Cambra <claudio.cambra@gmail.com>
Mon, 21 Mar 2022 16:34:21 +0000 (17:34 +0100)
committerMatthieu Gallien (Rebase PR Action) <matthieu_gallien@yahoo.fr>
Mon, 28 Mar 2022 09:21:35 +0000 (09:21 +0000)
Signed-off-by: Claudio Cambra <claudio.cambra@gmail.com>
20 files changed:
src/gui/application.cpp
src/gui/systray.cpp
src/gui/systray.h
src/gui/tray/ActivityItemContent.qml
src/gui/tray/UnifiedSearchResultListItem.qml
src/gui/tray/UserLine.qml
src/gui/tray/Window.qml
src/gui/tray/activitydata.cpp
src/gui/tray/activitydata.h
src/gui/tray/activitylistmodel.cpp
src/gui/tray/activitylistmodel.h
src/gui/tray/notificationhandler.cpp
src/gui/tray/unifiedsearchresult.h
src/gui/tray/unifiedsearchresultslistmodel.cpp
src/gui/tray/unifiedsearchresultslistmodel.h
src/libsync/theme.cpp
src/libsync/theme.h
test/testactivitylistmodel.cpp
theme.qrc.in
theme/Style/Style.qml

index e6dd3914260822587435ae184ca24945c1561af4..5320ade6945e3cb17ba34c38598544a6efb698e4 100644 (file)
@@ -105,6 +105,31 @@ namespace {
 
 // ----------------------------------------------------------------------------------
 
+#ifdef Q_OS_WIN
+class WindowsNativeEventFilter : public QAbstractNativeEventFilter {
+public:
+    bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) {
+        const auto msg = static_cast<MSG *>(message);
+        if(msg->message == WM_SYSCOLORCHANGE || msg->message == WM_SETTINGCHANGE) {
+            if(!_guiAppInstance) {
+                const auto ptr = qobject_cast<QGuiApplication *>(QGuiApplication::instance());
+                if(ptr) {
+                    _guiAppInstance.reset(ptr);
+                }
+            }
+
+            if(_guiAppInstance) {
+                emit _guiAppInstance->paletteChanged(_guiAppInstance->palette());
+            }
+        }
+        return false;
+    }
+
+private:
+    QScopedPointer<QGuiApplication> _guiAppInstance;
+};
+#endif
+
 bool Application::configVersionMigration()
 {
     QStringList deleteKeys, ignoreKeys;
@@ -192,6 +217,9 @@ Application::Application(int &argc, char **argv)
     // Ensure OpenSSL config file is only loaded from app directory
     QString opensslConf = QCoreApplication::applicationDirPath() + QString("/openssl.cnf");
     qputenv("OPENSSL_CONF", opensslConf.toLocal8Bit());
+
+    // Set up event listener for Windows theme changing
+    installNativeEventFilter(new WindowsNativeEventFilter());
 #endif
 
     // TODO: Can't set this without breaking current config paths
index fb9742830ae89be153c9d14e79cf1f7bc0ec3870..9e4831abe1151613de669cec7097f174547ee6b2 100644 (file)
@@ -130,12 +130,6 @@ Systray::Systray()
     });
 #endif
 
-    const auto ptr = qobject_cast<QGuiApplication *>(QGuiApplication::instance());
-    if(ptr) {
-        _guiAppInstance.reset(ptr);
-        connect(ptr, &QGuiApplication::paletteChanged, this, &Systray::darkModeChanged);
-    }
-
     connect(UserModel::instance(), &UserModel::newUserSelected,
         this, &Systray::slotNewUserSelected);
     connect(UserModel::instance(), &UserModel::addAccount,
@@ -231,24 +225,6 @@ bool Systray::useNormalWindow() const
     return cfg.showMainDialogAsNormalWindow();
 }
 
-bool Systray::darkMode()
-{
-#if defined(Q_OS_MACOS)
-    return osXInDarkMode();
-// Windows: Check registry for dark mode
-#elif defined(Q_OS_WIN)
-    const auto darkModeSubkey = QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
-    if (!Utility::registryKeyExists(HKEY_CURRENT_USER, darkModeSubkey)) {
-        return false;
-    }
-    const auto darkMode = !Utility::registryGetKeyValue(HKEY_CURRENT_USER, darkModeSubkey, QStringLiteral("AppsUseLightTheme")).toBool();
-    return darkMode;
-// Probably Linux
-#else
-    return Theme::isDarkColor(QGuiApplication::palette().window().color());
-#endif
-}
-
 Q_INVOKABLE void Systray::setOpened()
 {
     _isOpen = true;
index c0dc404adee2049bdbbc40cf4d59521702eeca53..d5cdce358656ab54599a7ee1c0f04c9ea5f1a641 100644 (file)
@@ -40,7 +40,6 @@ public:
 };
 
 #ifdef Q_OS_OSX
-bool osXInDarkMode();
 bool canOsXSendUserNotification();
 void sendOsXUserNotification(const QString &title, const QString &message);
 void setTrayWindowLevelAndVisibleOnAllSpaces(QWindow *window);
@@ -58,7 +57,6 @@ class Systray
 
     Q_PROPERTY(QString windowTitle READ windowTitle CONSTANT)
     Q_PROPERTY(bool useNormalWindow READ useNormalWindow CONSTANT)
-    Q_PROPERTY(bool darkMode READ darkMode NOTIFY darkModeChanged)
 
 public:
     static Systray *instance();
@@ -74,7 +72,6 @@ public:
     bool isOpen();
     QString windowTitle() const;
     bool useNormalWindow() const;
-    bool darkMode();
 
     Q_INVOKABLE void pauseResumeSync();
     Q_INVOKABLE bool syncIsPaused();
@@ -97,8 +94,6 @@ signals:
     void showFileActivityDialog(const QString &objectName, const int objectId);
     void sendChatMessage(const QString &token, const QString &message, const QString &replyTo);
 
-    void darkModeChanged();
-
 public slots:
     void slotNewUserSelected();
 
index 60e3fac60a3bc85707c7c5ba09fe084e52400067..4d779a52472b2e93ff92ddc2572d170a0093c617 100644 (file)
@@ -77,11 +77,7 @@ RowLayout {
             anchors.bottom: if(model.thumbnail !== undefined) parent.bottom
             cache: true
 
-            property string sourceUrl: Systray.darkMode ?
-                model.icon.replace("__COLOR__", "white").replace("__WHITE_GOES_HERE__", "-white") :
-                model.icon.replace("__COLOR__", "black").replace("__WHITE_GOES_HERE__", "")
-
-            source: sourceUrl
+            source: Theme.darkMode ? model.darkIcon : model.lightIcon
             sourceSize.height: 64
             sourceSize.width: 64
         }
@@ -146,9 +142,9 @@ RowLayout {
                 id: talkReplyMessage
                 anchors.fill: parent
             }
-        }         
-    }    
-    
+        }
+    }
+
     Button {
         id: dismissActionButton
 
@@ -184,7 +180,7 @@ RowLayout {
 
         contentItem: Image {
             anchors.fill: parent
-            source: parent.hovered ? Systray.darkMode ?
+            source: parent.hovered ? Theme.darkMode ?
                 "image://svgimage-custom-color/clear.svg/white" : "image://svgimage-custom-color/clear.svg/black" :
                 "image://svgimage-custom-color/clear.svg/grey"
             sourceSize.width: 24
index 785f8f52563054a71941753425820a6da8433e1c..ce942a4d15ea8055e5a45ff3291e89aeef489da6 100644 (file)
@@ -2,6 +2,7 @@ import QtQml 2.15
 import QtQuick 2.15
 import QtQuick.Controls 2.3
 import Style 1.0
+import com.nextcloud.desktopclient 1.0
 
 MouseArea {
     id: unifiedSearchResultMouseArea
@@ -60,8 +61,8 @@ MouseArea {
             height: unifiedSearchResultMouseArea.height
             title: model.resultTitle
             subline: model.subline
-            icons: model.icons
-            iconPlaceholder: model.imagePlaceholder
+            icons: Theme.darkMode ? model.darkIcons : model.lightIcons
+            iconPlaceholder: Theme.darkMode ? model.darkImagePlaceholder : model.lightImagePlaceholder
             isRounded: model.isRounded
             textLeftMargin: unifiedSearchResultMouseArea.textLeftMargin
             textRightMargin: unifiedSearchResultMouseArea.textRightMargin
index 3c25dc8658c56b63c2ccece06c6cd3c02f61c1dc..740cd1a9156853d478af511949a745c887b92e92 100644 (file)
@@ -61,7 +61,7 @@ MenuItem {
                     Layout.leftMargin: 7\r
                     verticalAlignment: Qt.AlignCenter\r
                     cache: false\r
-                    source: model.avatar != "" ? model.avatar : Systray.darkMode ? "image://avatars/fallbackWhite" : "image://avatars/fallbackBlack"\r
+                    source: model.avatar != "" ? model.avatar : Theme.darkMode ? "image://avatars/fallbackWhite" : "image://avatars/fallbackBlack"\r
                     Layout.preferredHeight: Style.accountAvatarSize\r
                     Layout.preferredWidth: Style.accountAvatarSize\r
                     Rectangle {\r
index 2cc13dd7b363302effd7489ea7dd4e4642512b88..53cfda8a1b8cc6c577d6d9299f4d5bb209d527b3 100644 (file)
@@ -239,7 +239,7 @@ Window {
                                 Image {\r
                                     Layout.leftMargin: 12\r
                                     verticalAlignment: Qt.AlignCenter\r
-                                    source: Systray.darkMode ? "qrc:///client/theme/white/add.svg" : "qrc:///client/theme/black/add.svg"\r
+                                    source: Theme.darkMode ? "qrc:///client/theme/white/add.svg" : "qrc:///client/theme/black/add.svg"\r
                                     sourceSize.width: Style.headerButtonIconSize\r
                                     sourceSize.height: Style.headerButtonIconSize\r
                                 }\r
index 8a077fc283ae86a9197fed16828430e6c0baeede..180a57f4e51cbafa44ae5fe27940de3dbfef6587 100644 (file)
@@ -63,9 +63,25 @@ OCC::Activity Activity::fromActivityJson(const QJsonObject json, const AccountPt
     activity._file = json.value(QStringLiteral("object_name")).toString();
     activity._link = QUrl(json.value(QStringLiteral("link")).toString());
     activity._dateTime = QDateTime::fromString(json.value(QStringLiteral("datetime")).toString(), Qt::ISODate);
-    activity._icon = json.value(QStringLiteral("icon")).toString();
+    activity._darkIcon = json.value(QStringLiteral("icon")).toString();  // We have both dark and light for theming purposes
+    activity._lightIcon = json.value(QStringLiteral("icon")).toString(); // Some icons get changed in the ActivityListModel
     activity._isCurrentUserFileActivity = activity._objectType == QStringLiteral("files") && activityUser == account->davUser();
 
+    const auto darkIconPath = QStringLiteral("qrc://:/client/theme/white/");
+    const auto lightIconPath = QStringLiteral("qrc://:/client/theme/black/");
+    if(activity._darkIcon.contains("change.svg")) {
+        activity._darkIcon = darkIconPath + QStringLiteral("change.svg");
+        activity._lightIcon = lightIconPath + QStringLiteral("change.svg");
+    } else if(activity._darkIcon.contains("calendar.svg")) {
+        activity._darkIcon = darkIconPath + QStringLiteral("calendar.svg");
+        activity._lightIcon = lightIconPath + QStringLiteral("calendar.svg");
+    } else if(activity._darkIcon.contains("personal.svg")) {
+        activity._darkIcon = darkIconPath + QStringLiteral("user.svg");
+        activity._lightIcon = lightIconPath + QStringLiteral("user.svg");
+    }  else if(activity._darkIcon.contains("core/img/actions")) {
+        activity._darkIcon.insert(activity._darkIcon.indexOf(".svg"), "-white");
+    }
+
     auto richSubjectData = json.value(QStringLiteral("subject_rich")).toArray();
 
     if(richSubjectData.size() > 1) {
@@ -128,15 +144,23 @@ OCC::Activity Activity::fromActivityJson(const QJsonObject json, const AccountPt
     }
 
     if(!previewsData.isEmpty()) {
-        if(activity._icon.contains(QStringLiteral("add-color.svg"))) {
-            activity._icon = "qrc:///client/theme/colored/add-bordered.svg";
-        } else if(activity._icon.contains(QStringLiteral("delete-color.svg"))) {
-            activity._icon = "qrc:///client/theme/colored/delete-bordered.svg";
-        } else if(activity._icon.contains(QStringLiteral("change.svg"))) {
-            activity._icon = "qrc:///client/theme/colored/change-bordered.svg";
+        if(activity._darkIcon.contains(QStringLiteral("add-color.svg"))) {
+            activity._darkIcon = "qrc:///client/theme/colored/add-bordered.svg";
+            activity._lightIcon = "qrc:///client/theme/colored/add-bordered.svg";
+        } else if(activity._darkIcon.contains(QStringLiteral("delete-color.svg"))) {
+            activity._darkIcon = "qrc:///client/theme/colored/delete-bordered.svg";
+            activity._lightIcon = "qrc:///client/theme/colored/add-bordered.svg";
+        } else if(activity._darkIcon.contains(QStringLiteral("change.svg"))) {
+            activity._darkIcon = "qrc:///client/theme/colored/change-bordered.svg";
+            activity._lightIcon = "qrc:///client/theme/colored/add-bordered.svg";
         }
     }
 
+    auto actions = json.value("actions").toArray();
+    foreach (auto action, actions) {
+        activity._links.append(ActivityLink::createFomJsonObject(action.toObject()));
+    }
+
     return activity;
 }
 
index e9f1b91922a51399a71107b0c85621440cd1ab42..bd7b49ffc055959ab86e1d79b72fc5b6c8591470 100644 (file)
@@ -133,7 +133,8 @@ public:
     QDateTime _dateTime;
     qint64 _expireAtMsecs = -1;
     QString _accName;
-    QString _icon;
+    QString _darkIcon;
+    QString _lightIcon;
     bool _isCurrentUserFileActivity = false;
     QVector<PreviewData> _previews;
 
index 63733b8be2288d76bd1f052c53d194279ba68c55..ff50ae0d0c239368ff747f47f27b9b837f064a38 100644 (file)
@@ -62,7 +62,8 @@ QHash<int, QByteArray> ActivityListModel::roleNames() const
     roles[LinkRole] = "link";
     roles[MessageRole] = "message";
     roles[ActionRole] = "type";
-    roles[ActionIconRole] = "icon";
+    roles[DarkIconRole] = "darkIcon";
+    roles[LightIconRole] = "lightIcon";
     roles[ActionTextRole] = "subject";
     roles[ActionsLinksRole] = "links";
     roles[ActionsLinksContextMenuRole] = "linksContextMenu";
@@ -192,31 +193,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
         });
     };
 
-    switch (role) {
-    case DisplayPathRole:
-        return getDisplayPath();
-    case PathRole:
-        return QFileInfo(getFilePath()).path();
-    case DisplayLocationRole:
-        return displayLocation();
-    case ActionsLinksRole: {
-        QList<QVariant> customList;
-        foreach (ActivityLink activityLink, a._links) {
-            customList << QVariant::fromValue(activityLink);
-        }
-        return customList;
-    }
-
-    case ActionsLinksContextMenuRole: {
-        return ActivityListModel::convertLinksToMenuEntries(a);
-    }
-    
-    case ActionsLinksForActionButtonsRole: {
-        return ActivityListModel::convertLinksToActionButtons(a);
-    }
-
-    case ActionIconRole: {
-        auto colorIconPath = QStringLiteral("qrc:///client/theme/__COLOR__/"); // We will replace __COLOR__ in QML
+    const auto generateIconPath = [&]() {
+        auto colorIconPath = role == DarkIconRole ? QStringLiteral("qrc:///client/theme/white/") : QStringLiteral("qrc:///client/theme/black/");
         if (a._type == Activity::NotificationType) {
             colorIconPath.append("bell.svg");
             return colorIconPath;
@@ -255,14 +233,40 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
             }
         } else {
             // We have an activity
-            if (a._icon.isEmpty()) {
+            if (a._darkIcon.isEmpty()) {
                 colorIconPath.append("activity.svg");
                 return colorIconPath;
             }
+            return role == DarkIconRole ? a._darkIcon : a._lightIcon;
+        }
+    };
 
-            return a._icon;
+    switch (role) {
+    case DisplayPathRole:
+        return getDisplayPath();
+    case PathRole:
+        return QFileInfo(getFilePath()).path();
+    case DisplayLocationRole:
+        return displayLocation();
+    case ActionsLinksRole: {
+        QList<QVariant> customList;
+        foreach (ActivityLink activityLink, a._links) {
+            customList << QVariant::fromValue(activityLink);
         }
+        return customList;
+    }
+
+    case ActionsLinksContextMenuRole: {
+        return ActivityListModel::convertLinksToMenuEntries(a);
+    }
+    
+    case ActionsLinksForActionButtonsRole: {
+        return ActivityListModel::convertLinksToActionButtons(a);
     }
+
+    case DarkIconRole:
+    case LightIconRole:
+        return generateIconPath();
     case ObjectTypeRole:
         return a._objectType;
     case ObjectIdRole:
@@ -400,20 +404,6 @@ void ActivityListModel::ingestActivities(const QJsonArray &activities)
 
         auto a = Activity::fromActivityJson(json, _accountState->account());
 
-        auto colorIconPath = QStringLiteral("qrc:///client/theme/__COLOR__/");
-        if(a._icon.contains("change.svg")) {
-            colorIconPath.append("change.svg");
-            a._icon = colorIconPath;
-        } else if(a._icon.contains("calendar.svg")) {
-            colorIconPath.append("calendar.svg");
-            a._icon = colorIconPath;
-        } else if(a._icon.contains("personal.svg")) {
-            colorIconPath.append("user.svg");
-            a._icon = colorIconPath;
-        }  else if(a._icon.contains("core/img/actions")) {
-            a._icon.insert(a._icon.indexOf(".svg"), "__WHITE_GOES_HERE__");
-        }
-
         list.append(a);
         _currentItem = list.last()._id;
 
index 44b521352e5726618100528669de6ba1cf058cab..ee6959f60c8e2c556a1c6a69e3f0ff1e1d5f99f1 100644 (file)
@@ -45,7 +45,8 @@ class ActivityListModel : public QAbstractListModel
     Q_PROPERTY(AccountState *accountState READ accountState CONSTANT)
 public:
     enum DataRole {
-        ActionIconRole = Qt::UserRole + 1,
+        DarkIconRole = Qt::UserRole + 1,
+        LightIconRole,
         AccountRole,
         ObjectTypeRole,
         ObjectIdRole,
index aa5bd41554efe9447161e37ec241eac978766924..0e79d13ad9aa0149565388baa57ca9a111cd4161 100644 (file)
@@ -92,15 +92,11 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
     ActivityList list;
 
     foreach (auto element, notifies) {
-        Activity a;
         auto json = element.toObject();
+        auto a = Activity::fromActivityJson(json, ai->account());
         a._type = Activity::NotificationType;
-        a._accName = ai->account()->displayName();
         a._id = json.value("notification_id").toInt();
 
-        //need to know, specially for remote_share
-        a._objectType = json.value("object_type").toString();
-
         // 2 cases to consider:
         // - server == 24 & has Talk: notification type chat/call contains conversationToken/messageId in object_type
         // - server < 24 & has Talk: notification type chat/call contains _only_ the conversationToken in object_type
@@ -117,10 +113,6 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
 
         a._status = 0;
 
-        a._subject = json.value("subject").toString();
-        a._message = json.value("message").toString();
-        a._icon = json.value("icon").toString();
-
         QUrl link(json.value("link").toString());
         if (!link.isEmpty()) {
             if (link.host().isEmpty()) {
@@ -132,12 +124,6 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
             }
         }
         a._link = link;
-        a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate);
-
-        auto actions = json.value("actions").toArray();
-        foreach (auto action, actions) {
-            a._links.append(ActivityLink::createFomJsonObject(action.toObject()));
-        }
 
         // Add another action to dismiss notification on server
         // https://github.com/owncloud/notifications/blob/master/docs/ocs-endpoint-v1.md#deleting-a-notification-for-a-user
index bae3158d634b8e3f84d7a9e818c246cde57e2d15..643c6d91c0a2aa90c308c04a22ae816184610749 100644 (file)
@@ -42,7 +42,8 @@ struct UnifiedSearchResult
     bool _isRounded = false;
     qint32 _order = std::numeric_limits<qint32>::max();
     QUrl _resourceUrl;
-    QString _icons;
+    QString _darkIcons;
+    QString _lightIcons;
     Type _type = Type::Default;
 };
 }
index 708acb7dedec8d05718cf00245815521e946e543..7d5cd95db1c4ef2e32cc9bf6fe16dbcf7abe1e65 100644 (file)
@@ -19,7 +19,6 @@
 #include "guiutility.h"
 #include "folderman.h"
 #include "networkjobs.h"
-#include "systray.h"
 
 #include <algorithm>
 
 #include <QDesktopServices>
 
 namespace {
-QString imagePlaceholderUrlForProviderId(const QString &providerId)
+QString imagePlaceholderUrlForProviderId(const QString &providerId, const bool darkMode)
 {
+    const auto colorIconPath = darkMode ? QStringLiteral(":/client/theme/white/") : QStringLiteral(":/client/theme/black/");
     if (providerId.contains(QStringLiteral("message"), Qt::CaseInsensitive)
         || providerId.contains(QStringLiteral("talk"), Qt::CaseInsensitive)) {
-        return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/wizard-talk.svg") : QStringLiteral("qrc:///client/theme/black/wizard-talk.svg");
+        return colorIconPath % QStringLiteral("wizard-talk.svg");
     } else if (providerId.contains(QStringLiteral("file"), Qt::CaseInsensitive)) {
-        return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/edit.svg") : QStringLiteral("qrc:///client/theme/black/edit.svg");
+        return colorIconPath % QStringLiteral("edit.svg");
     } else if (providerId.contains(QStringLiteral("deck"), Qt::CaseInsensitive)) {
-        return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/deck.svg") : QStringLiteral("qrc:///client/theme/black/deck.svg");
+        return colorIconPath % QStringLiteral("deck.svg");
     } else if (providerId.contains(QStringLiteral("calendar"), Qt::CaseInsensitive)) {
-        return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/calendar.svg") : QStringLiteral("qrc:///client/theme/black/calendar.svg");
+        return colorIconPath % QStringLiteral("calendar.svg");
     } else if (providerId.contains(QStringLiteral("mail"), Qt::CaseInsensitive)) {
-        return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/email.svg") : QStringLiteral("qrc:///client/theme/black/email.svg");
+        return colorIconPath % QStringLiteral("email.svg");
     } else if (providerId.contains(QStringLiteral("comment"), Qt::CaseInsensitive)) {
-        return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/comment.svg") : QStringLiteral("qrc:///client/theme/black/comment.svg");
+        return colorIconPath % QStringLiteral("comment.svg");
     }
 
-    return QStringLiteral("qrc:///client/theme/change.svg");
+    return colorIconPath % QStringLiteral("change.svg");
 }
 
-QString localIconPathFromIconPrefix(const QString &iconNameWithPrefix)
+QString localIconPathFromIconPrefix(const QString &iconNameWithPrefix, const bool darkMode)
 {
+    const auto colorIconPath = darkMode ? QStringLiteral(":/client/theme/white/") : QStringLiteral(":/client/theme/black/");
     if (iconNameWithPrefix.contains(QStringLiteral("message"), Qt::CaseInsensitive)
         || iconNameWithPrefix.contains(QStringLiteral("talk"), Qt::CaseInsensitive)) {
-        return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/wizard-talk.svg") : QStringLiteral(":/client/theme/black/wizard-talk.svg");
+        return colorIconPath % QStringLiteral("wizard-talk.svg");
     } else if (iconNameWithPrefix.contains(QStringLiteral("folder"), Qt::CaseInsensitive)) {
-        return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/folder.svg") : QStringLiteral(":/client/theme/black/folder.svg");
+        return colorIconPath % QStringLiteral("folder.svg");
     } else if (iconNameWithPrefix.contains(QStringLiteral("deck"), Qt::CaseInsensitive)) {
-        return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/deck.svg") : QStringLiteral(":/client/theme/black/deck.svg");
+        return colorIconPath % QStringLiteral("deck.svg");
     } else if (iconNameWithPrefix.contains(QStringLiteral("contacts"), Qt::CaseInsensitive)) {
-        return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/wizard-groupware.svg") : QStringLiteral(":/client/theme/black/wizard-groupware.svg");
+        return colorIconPath % QStringLiteral("wizard-groupware.svg");
     } else if (iconNameWithPrefix.contains(QStringLiteral("calendar"), Qt::CaseInsensitive)) {
-        return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/calendar.svg") : QStringLiteral(":/client/theme/black/calendar.svg");
+        return colorIconPath % QStringLiteral("calendar.svg");
     } else if (iconNameWithPrefix.contains(QStringLiteral("mail"), Qt::CaseInsensitive)) {
-        return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/email.svg") : QStringLiteral(":/client/theme/black/email.svg");
+        return colorIconPath % QStringLiteral("email.svg");
     }
 
-    return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/change.svg") : QStringLiteral(":/client/theme/change.svg");
+    return colorIconPath % QStringLiteral("change.svg");
 }
 
-QString iconUrlForDefaultIconName(const QString &defaultIconName)
+QString iconUrlForDefaultIconName(const QString &defaultIconName, const bool darkMode)
 {
     const QUrl urlForIcon{defaultIconName};
 
@@ -75,15 +76,16 @@ QString iconUrlForDefaultIconName(const QString &defaultIconName)
         return defaultIconName;
     }
     
+    const auto colorIconPath = darkMode ? QStringLiteral(":/client/theme/white/") : QStringLiteral(":/client/theme/black/");
+
     if (defaultIconName.startsWith(QStringLiteral("icon-"))) {
         const auto parts = defaultIconName.split(QLatin1Char('-'));
 
         if (parts.size() > 1) {
-            const QString blackOrWhite = OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/") : QStringLiteral(":/client/theme/black/");
-            const QString blackIconFilePath = blackOrWhite + parts[1] + QStringLiteral(".svg");
+            const QString blackOrWhiteIconFilePath = colorIconPath + parts[1] + QStringLiteral(".svg");
 
-            if (QFile::exists(blackIconFilePath)) {
-                return blackIconFilePath;
+            if (QFile::exists(blackOrWhiteIconFilePath)) {
+                return blackOrWhiteIconFilePath;
             }
 
             const QString iconFilePath = QStringLiteral(":/client/theme/") + parts[1] + QStringLiteral(".svg");
@@ -93,14 +95,14 @@ QString iconUrlForDefaultIconName(const QString &defaultIconName)
             }
         }
 
-        const auto iconNameFromIconPrefix = localIconPathFromIconPrefix(defaultIconName);
+        const auto iconNameFromIconPrefix = localIconPathFromIconPrefix(defaultIconName, darkMode);
 
         if (!iconNameFromIconPrefix.isEmpty()) {
             return iconNameFromIconPrefix;
         }
     }
 
-    return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/change.svg") : QStringLiteral(":/client/theme/change.svg");
+    return colorIconPath % QStringLiteral("change.svg");
 }
 
 QString generateUrlForThumbnail(const QString &thumbnailUrl, const QUrl &serverUrl)
@@ -125,7 +127,7 @@ QString generateUrlForThumbnail(const QString &thumbnailUrl, const QUrl &serverU
     return thumbnailUrlCopy;
 }
 
-QString generateUrlForIcon(const QString &fallbackIcon, const QUrl &serverUrl)
+QString generateUrlForIcon(const QString &fallbackIcon, const QUrl &serverUrl, const bool darkMode)
 {
     auto serverUrlCopy = serverUrl;
 
@@ -144,7 +146,7 @@ QString generateUrlForIcon(const QString &fallbackIcon, const QUrl &serverUrl)
         }
     } else if (!fallbackIconCopy.isEmpty()) {
         // could be one of names for standard icons (e.g. icon-mail)
-        const auto defaultIconUrl = iconUrlForDefaultIconName(fallbackIconCopy);
+        const auto defaultIconUrl = iconUrlForDefaultIconName(fallbackIconCopy, darkMode);
         if (!defaultIconUrl.isEmpty()) {
             fallbackIconCopy = defaultIconUrl;
         }
@@ -153,7 +155,7 @@ QString generateUrlForIcon(const QString &fallbackIcon, const QUrl &serverUrl)
     return fallbackIconCopy;
 }
 
-QString iconsFromThumbnailAndFallbackIcon(const QString &thumbnailUrl, const QString &fallbackIcon, const QUrl &serverUrl)
+QString iconsFromThumbnailAndFallbackIcon(const QString &thumbnailUrl, const QString &fallbackIcon, const QUrl &serverUrl, const bool darkMode)
 {
     if (thumbnailUrl.isEmpty() && fallbackIcon.isEmpty()) {
         return {};
@@ -165,7 +167,7 @@ QString iconsFromThumbnailAndFallbackIcon(const QString &thumbnailUrl, const QSt
     }
 
     const auto urlForThumbnail = generateUrlForThumbnail(thumbnailUrl, serverUrl);
-    const auto urlForFallbackIcon = generateUrlForIcon(fallbackIcon, serverUrl);
+    const auto urlForFallbackIcon = generateUrlForIcon(fallbackIcon, serverUrl, darkMode);
 
     qDebug() << "SEARCH" << urlForThumbnail << urlForFallbackIcon;
 
@@ -204,10 +206,14 @@ QVariant UnifiedSearchResultsListModel::data(const QModelIndex &index, int role)
         return _results.at(index.row())._providerName;
     case ProviderIdRole: 
         return _results.at(index.row())._providerId;
-    case ImagePlaceholderRole:
-        return imagePlaceholderUrlForProviderId(_results.at(index.row())._providerId);
-    case IconsRole:
-        return _results.at(index.row())._icons;
+    case DarkImagePlaceholderRole:
+        return imagePlaceholderUrlForProviderId(_results.at(index.row())._providerId, true);
+    case LightImagePlaceholderRole:
+        return imagePlaceholderUrlForProviderId(_results.at(index.row())._providerId, false);
+    case DarkIconsRole:
+        return _results.at(index.row())._darkIcons;
+    case LightIconsRole:
+        return _results.at(index.row())._lightIcons;
     case TitleRole:
         return _results.at(index.row())._title;
     case SublineRole:
@@ -239,8 +245,10 @@ QHash<int, QByteArray> UnifiedSearchResultsListModel::roleNames() const
     auto roles = QAbstractListModel::roleNames();
     roles[ProviderNameRole] = "providerName";
     roles[ProviderIdRole] = "providerId";
-    roles[IconsRole] = "icons";
-    roles[ImagePlaceholderRole] = "imagePlaceholder";
+    roles[DarkIconsRole] = "darkIcons";
+    roles[LightIconsRole] = "lightIcons";
+    roles[DarkImagePlaceholderRole] = "darkImagePlaceholder";
+    roles[LightImagePlaceholderRole] = "lightImagePlaceholder";
     roles[TitleRole] = "resultTitle";
     roles[SublineRole] = "subline";
     roles[ResourceUrlRole] = "resourceUrlRole";
@@ -577,8 +585,10 @@ void UnifiedSearchResultsListModel::parseResultsForProvider(const QJsonObject &d
         const auto accountUrl = (_accountState && _accountState->account()) ? _accountState->account()->url() : QUrl();
 
         result._resourceUrl = makeResourceUrl(resourceUrl, accountUrl);
-        result._icons = iconsFromThumbnailAndFallbackIcon(entryMap.value(QStringLiteral("thumbnailUrl")).toString(),
-            entryMap.value(QStringLiteral("icon")).toString(), accountUrl);
+        result._darkIcons = iconsFromThumbnailAndFallbackIcon(entryMap.value(QStringLiteral("thumbnailUrl")).toString(),
+            entryMap.value(QStringLiteral("icon")).toString(), accountUrl, true);
+        result._lightIcons = iconsFromThumbnailAndFallbackIcon(entryMap.value(QStringLiteral("thumbnailUrl")).toString(),
+            entryMap.value(QStringLiteral("icon")).toString(), accountUrl, false);
 
         newEntries.push_back(result);
     }
index 5ae811f205729af08ad7d3548511f34e37f97a35..2fffd99da39e7173dbe9c3b46dab10c25c6beb22 100644 (file)
@@ -53,8 +53,10 @@ public:
     enum DataRole {
         ProviderNameRole = Qt::UserRole + 1,
         ProviderIdRole,
-        ImagePlaceholderRole,
-        IconsRole,
+        DarkImagePlaceholderRole,
+        LightImagePlaceholderRole,
+        DarkIconsRole,
+        LightIconsRole,
         TitleRole,
         SublineRole,
         ResourceUrlRole,
index 8916cba2f98d2c4723bdb233459414cf64198144..34bea7287003865028c590f24c2ada704a6dde00 100644 (file)
@@ -343,6 +343,9 @@ QString Theme::hidpiFileName(const QString &iconName, const QColor &backgroundCo
 Theme::Theme()
     : QObject(nullptr)
 {
+#if defined(Q_OS_WIN)
+    reserveDarkPalette = QPalette(QColor(49,49,49,255), QColor(35,35,35,255)); // Windows 11 button and window dark colours
+#endif
 }
 
 // If this option returns true, the client only supports one folder to sync.
@@ -899,16 +902,44 @@ QColor Theme::errorBoxBorderColor() const
     return QColor{"black"};
 }
 
-QPalette Theme::systemPalette()
+void Theme::connectToPaletteSignal()
 {
     if(!_guiAppInstance) {
         const auto ptr = qobject_cast<QGuiApplication *>(QGuiApplication::instance());
         if(ptr) {
             _guiAppInstance.reset(ptr);
-            connect(ptr, &QGuiApplication::paletteChanged, this, &Theme::systemPaletteChanged);
+            connect(_guiAppInstance.data(), &QGuiApplication::paletteChanged, this, &Theme::systemPaletteChanged);
+            connect(_guiAppInstance.data(), &QGuiApplication::paletteChanged, this, &Theme::darkModeChanged);
         }
     }
+}
+
+QPalette Theme::systemPalette()
+{
+    connectToPaletteSignal();
+#if defined(Q_OS_WIN)
+    if(darkMode()) {
+        return reserveDarkPalette;
+    }
+#endif
     return QGuiApplication::palette();
 }
 
+bool Theme::darkMode()
+{
+    connectToPaletteSignal();
+// Windows: Check registry for dark mode
+#if defined(Q_OS_WIN)
+    const auto darkModeSubkey = QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
+    if (Utility::registryKeyExists(HKEY_CURRENT_USER, darkModeSubkey) &&
+        !Utility::registryGetKeyValue(HKEY_CURRENT_USER, darkModeSubkey, QStringLiteral("AppsUseLightTheme")).toBool()) {
+        return true;
+    }
+
+    return false;
+#else
+    return Theme::isDarkColor(QGuiApplication::palette().window().color());
+#endif
+}
+
 } // end namespace client
index d6c38aacb05634b430fa50e80cf7de54d1318909..df9303b2362a4863ae77742e1d2ad252bbbcf560 100644 (file)
@@ -69,6 +69,7 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject
     Q_PROPERTY(QColor errorBoxBorderColor READ errorBoxBorderColor CONSTANT)
 
     Q_PROPERTY(QPalette systemPalette READ systemPalette NOTIFY systemPaletteChanged)
+    Q_PROPERTY(bool darkMode READ darkMode NOTIFY darkModeChanged)
 public:
     enum CustomMediaType {
         oCSetupTop, // ownCloud connect page
@@ -594,6 +595,7 @@ public:
     static constexpr const char *themePrefix = ":/client/theme/";
 
     QPalette systemPalette();
+    bool darkMode();
 
 protected:
 #ifndef TOKEN_AUTH_ONLY
@@ -612,14 +614,21 @@ protected:
 signals:
     void systrayUseMonoIconsChanged(bool);
     void systemPaletteChanged(const QPalette &palette);
+    void darkModeChanged();
 
 private:
     Theme(Theme const &);
     Theme &operator=(Theme const &);
 
+    void connectToPaletteSignal();
+#if defined(Q_OS_WIN)
+    QPalette reserveDarkPalette; // Windows 11 button and window dark colours
+#endif
+
     static Theme *_instance;
     bool _mono = false;
     QScopedPointer<QGuiApplication> _guiAppInstance;
+
 #ifndef TOKEN_AUTH_ONLY
     mutable QHash<QString, QIcon> _iconCache;
 #endif
index fd521f40699d797b29fb76956a5c38c33ef20a60..24c75941e65898a3d0a8a88d02ac83547026ecca 100644 (file)
@@ -598,7 +598,8 @@ private slots:
 
             QVERIFY(!index.data(OCC::ActivityListModel::AccountRole).toString().isEmpty());
             QVERIFY(!index.data(OCC::ActivityListModel::ActionTextColorRole).toString().isEmpty());
-            QVERIFY(!index.data(OCC::ActivityListModel::ActionIconRole).toString().isEmpty());
+            QVERIFY(!index.data(OCC::ActivityListModel::DarkIconRole).toString().isEmpty());
+            QVERIFY(!index.data(OCC::ActivityListModel::LightIconRole).toString().isEmpty());
             QVERIFY(!index.data(OCC::ActivityListModel::PointInTimeRole).toString().isEmpty());
 
             QVERIFY(index.data(OCC::ActivityListModel::ObjectTypeRole).canConvert<int>());
index 3823b8da1545b0725d6b73dbc2f42e99b7f90465..6f42f52213fb1509e16c3182ad79f12bbb29f036 100644 (file)
@@ -44,6 +44,7 @@
         <file>theme/white/state-sync-64.png</file>
         <file>theme/white/state-sync-128.png</file>
         <file>theme/white/state-sync-256.png</file>
+        <file>theme/black/change.svg</file>
         <file>theme/black/clear.svg</file>
         <file>theme/black/comment.svg</file>
         <file>theme/black/search.svg</file>
@@ -90,6 +91,7 @@
         <file>theme/white/folder@2x.png</file>
         <file>theme/colored/folder.png</file>
         <file>theme/colored/folder@2x.png</file>
+        <file>theme/black/confirm.svg</file>
         <file>theme/black/control-next.svg</file>
         <file>theme/black/control-prev.svg</file>
         <file>theme/black/settings.svg</file>
index 5cb2220c465c293d3fe7b770e2cefa030394f5b2..6c90be5637f0552b5eae124bd5a9eca5910faa5d 100644 (file)
@@ -12,8 +12,8 @@ QtObject {
     readonly property color ncTextColor: Theme.systemPalette.windowText\r
     readonly property color ncSecondaryTextColor: "#808080"\r
     readonly property color ncHeaderTextColor: "white"\r
-    readonly property color lightHover:  Systray.darkMode ? Qt.lighter(backgroundColor, 2) : Qt.darker(backgroundColor, 1.05)\r
-    readonly property color menuBorder:  Systray.darkMode ? Qt.lighter(backgroundColor, 3) : Qt.darker(backgroundColor, 1.5)\r
+    readonly property color lightHover: Theme.darkMode ? Qt.lighter(backgroundColor, 2) : Qt.darker(backgroundColor, 1.05)\r
+    readonly property color menuBorder: ncSecondaryTextColor\r
     readonly property color backgroundColor: Theme.systemPalette.base\r
 \r
     // ErrorBox colors\r